Skip to main content

Backplanes Python SDK

The Backplanes Python SDK lets Python agents register with a Backplanes collector, capture LLM and HTTP telemetry automatically, propagate workflow context between agents, and persist agent identity locally — with one call to set up.

Table Of Contents

Install

pip install backplanes

For local development:

uv pip install -e .

Quick Start

Get a key file from the dashboard: Settings → API keys → Create key → Download key file, and save it as ~/.backplanes/backplanes-key.json. (Or set BACKPLANES_ORG_API_KEY — the key file additionally carries a signing key, which you need to hand workflows to other agents.)

export BACKPLANES_ORG_API_KEY="OK_..."   # alternative to the key file

Then instrument your agent with one call:

import anthropic
import backplanes

backplanes.init(agent_name="my-agent")

sdk = anthropic.Anthropic()
response = sdk.messages.create(
    model="claude-haiku-4-5-20251001",
    max_tokens=50,
    messages=[{"role": "user", "content": "Say hello in five words."}],
)

That's the whole integration. Every Anthropic or OpenAI client constructed after init() is instrumented automatically — the call above lands in the dashboard as an llm event with provider, resolved model, and prompt/completion token counts, including for streaming calls. This script contains zero hand-written telemetry.

init() returns a BackplanesClient for everything beyond automatic capture — starting workflows, custom events, context propagation:

client = backplanes.init(agent_name="my-agent")
workflow_id = client.start_workflow()

How Instrumentation Works

Instrumentation is three composable layers, highest fidelity first. See docs/design/INSTRUMENTATION_TIERS.md for the full design.

SDK wrappers (wrap_anthropic, wrap_openai) wrap an SDK client instance's own create/stream methods, recording each call at the semantic level: resolved model, prompt and completion tokens, and cache tokens. Streaming responses are observed chunk by chunk, so token counts land even for streamed calls. Use these directly when you want explicit per-client control:

import anthropic
import backplanes

client = backplanes.init(agent_name="my-agent", auto_instrument=False)
sdk = backplanes.wrap_anthropic(anthropic.Anthropic(), client)

init() constructor hooks give you wrapper fidelity without touching client construction: init() hooks the Anthropic/OpenAI SDK constructors, so every client built afterwards comes out wrapped. Clients constructed before init() fall through to the transport net.

The transport net (patch_httpx, patch_requests) instruments outgoing HTTP at the httpx (sync and async) and requests level. It catches everything the wrappers don't — unknown SDKs, raw HTTP, pre-init() clients — recognizing known inference endpoints and emitting llm events with provider, model, and token counts where the response allows. It also carries Backplanes-Context propagation to every destination. Streamed responses caught only at this level record the call but not token usage, and are marked as such in event metadata.

The layers compose because of one dedup guarantee: one logical call produces one event. Wrappers mark the duration of the underlying SDK call, and the transport net demotes itself inside that window — context propagation still runs, event emission does not. An SDK's internal retries land inside the same window, so they never double-count.

Modes

Workflow Root

Use workflow root mode when your agent starts workflows. This needs an org API key, passed directly or read from BACKPLANES_ORG_API_KEY.

client = backplanes.init(
    org_api_key="OK_...",
    agent_name="my-agent",
)

A key file bundles the same org API key with an Ed25519 signing key, and the client reads both from it:

client = backplanes.init(
    config_file="backplanes-key.json",
    agent_name="my-agent",
)

Without an explicit config_file, the client searches BACKPLANES_KEY_FILE, ./backplanes-key.json, and ~/.backplanes/backplanes-key.json — see docs/CONFIGURATION.md.

Propagating To Other Agents

Handing a workflow to a second agent means signing a Backplanes-Context, and that needs the signing key from a key file. An org API key on its own won't do it. can_propagate_context tells you which you have. Without a signing key each agent's events still carry their own workflow ID, they just aren't linked into one chain.

The Backplanes-Context header identifies your org, workflow, and agent chain, and the transport patches send it to every destination:

backplanes.init(agent_name="my-agent")

That is what lets a chain link without anyone describing the topology first, and it has a disclosure consequence worth being explicit about: the same process usually calls both your own services and third-party APIs, and those third parties receive the header too.

Mid-Chain Agent

Use mid-chain mode when your agent receives a Backplanes-Context header from an upstream agent:

client = backplanes.init(agent_name="worker-agent")

context_jwt = request.headers.get("Backplanes-Context")
with client.request_context(context_jwt):
    ...  # work in this scope is attributed to the upstream workflow

Local Development

By default the client targets api.backplanes.com:443 with TLS enabled. To point the same code at a different collector, use the environment:

export BACKPLANES_COLLECTOR_HOST=localhost
export BACKPLANES_COLLECTOR_PORT=50051

For a collector behind a private certificate authority, keep TLS on and point BACKPLANES_TLS_CA_FILE at the CA bundle — gRPC does not read the OS trust store. The same settings are available as constructor parameters (collector_host, collector_port, tls_ca_cert); precedence is explicit parameter > key file > environment > default.

Claiming And Unclaimed Mode

A first run without org credentials registers the agent and logs a claim URL. Claim it in the dashboard to link the agent to your organization; the client picks the claim up on its next call and starts sending. With a key file or BACKPLANES_ORG_API_KEY in place, the agent is active immediately and no claiming step is needed.

Until an agent is claimed:

  • start_workflow() returns a placeholder workflow ID
  • get_context_header() returns {} because there is no active signed workflow context
  • ingest() returns a stub response instead of sending events

An agent leaves unclaimed mode two ways:

  • A key file appears in one of the standard search paths. The client hot-reloads it on the next call and gains org credentials and signing keys, so it can also mint Backplanes-Context JWTs for downstream agents.
  • The collector reports the agent as claimed. refresh_claim_status() asks it directly, and the client polls at most once a minute during normal calls. This is what happens when you claim an agent from the dashboard, where there's no key file to download. The client can ingest and stamps its org onto events. Signing context JWTs still needs a key file.

Configuration And Identity

Every collector setting resolves from constructor parameters, the key file, and the environment, in that order. Key files only override the collector settings they actually specify. Named agents get their own identity file (~/.backplanes/identities/<agent_name>.json), so several agents on one machine stay distinct identities. The full environment variable table, key file schema, and identity file locations are in docs/CONFIGURATION.md.

Running Alongside Your Application

If the collector has problems, your application should keep working.

  • Construction does not raise by default. When the collector is unreachable the client logs a warning and starts up with telemetry off, retrying registration in the background. Pass strict=True if you would rather it raise BackplanesNotReadyError — useful in CI, where silent telemetry is worse than a failed build.

  • is_ready answers "would an event reach the collector". It is False while the agent is waiting to be claimed, which is where every new agent starts. has_identity is the narrower question of whether registration succeeded. An unclaimed client warns, so this state is visible without checking.

  • Every call has a timeout, 10 seconds by default and configurable. A host that drops packets silently will fail the call instead of hanging your thread.

  • Ingest never blocks you. ingest() starts the call and hands back the future for it. gRPC does the network work on its own threads. Ask for the answer only when you want it:

    client.ingest(event)                  # fire and forget
    client.ingest(event).result()         # wait for the collector's counts
    

    At interpreter exit the client waits up to three seconds for whatever is still in flight, so a short script usually doesn't need to do anything. That wait is best effort and gives up quietly — if you need to know an event was accepted, call result(). flush(timeout=...) waits for everything outstanding and returns False if it ran out of time, which is what you want before returning from a serverless handler. A call nobody waits on logs its failure, since there's nobody to raise to.

Custom Events

Automatic instrumentation covers LLM and HTTP traffic. For everything else — tool invocations, internal pipeline stages, domain-specific events — create_event(...) and ingest(...) are still there:

client = backplanes.init(agent_name="my-agent")

event = client.create_event(
    edge_type="tool",
    direction="egress",
    status="ok",
    metadata={"tool.name": "send_email"},
)
client.ingest(event)

If your agent is handling an incoming Backplanes workflow, wrap the work in request_context(...) so events are attributed to the upstream chain:

context_jwt = request.headers["Backplanes-Context"]
with client.request_context(context_jwt):
    client.ingest(client.create_event(edge_type="tool", direction="ingress"))

create_event(...) supports the full event schema — sessions, instance IDs, parent events, PII fields, token usage, error info, custom metadata. See docs/ADVANCED.md, and examples/email_campaign for tool and PII events in a real workflow.

What The SDK Covers

  • init(...) — one-call setup: constructs the client, hooks the Anthropic/OpenAI SDK constructors, installs the transport net
  • wrap_anthropic(...) / wrap_openai(...) — semantic wrappers for explicit per-client instrumentation, with streaming token capture and cache token metadata; the OpenAI wrapper covers chat.completions, responses, and embeddings, and attributes OpenAI-compatible endpoints (gateways, Groq, and the like) by base_url
  • patch_httpx(...) — the transport net for anything built on httpx, sync and async clients. Recognized inference endpoints emit an llm edge with provider, resolved model, and token counts pulled from the response
  • patch_requests(...) — the same net for outgoing requests calls
  • instrument_fastapi(...), instrument_asgi(...), and instrument_wsgi(...) — one-line framework setup for inbound context and ingress telemetry (Django is WSGI/ASGI)
  • request_context(...) — request-scoped propagation in web apps and workers
  • create_event(...) — the full event schema, including session_id, instance IDs, parent_event_id, PII fields, schema_version, token usage, error info, and custom metadata
  • create_batch(...), ingest_batch(...), and ingest_stream(...) — explicit batch construction, unary batch ingest, and the collector's client-streaming RPC

Both HTTP patches record calls to hosts you don't own against an ID derived from the hostname, so you can see which third party received the data. That is unconditional — it needs no argument, and sends nothing to the third party.

The Backplanes-Context header is also unconditional: every destination gets the org, workflow, and agent chain while a workflow context is active. That includes third-party APIs, so enable the transport patches only where that disclosure is acceptable:

patch_httpx(client)
patch_requests(client)

You can also import the generated proto modules directly:

from backplanes.v1 import collector_pb2, events_pb2

Examples

Runnable, real-world examples — from a five-line quickstart to a multi-agent workflow with context propagation — live in examples/, each with its own README.

Docs

Focused docs live in docs/:

Development

uv sync
uv build
uv run ruff check .
uv run pytest

Regenerating Protobuf Bindings

backplanes/v1/ is generated from the Backplanes proto/ buf module and checked in, so installing this package needs neither buf nor protoc. To pick up proto changes, point BACKPLANES_PROTO_REPO at a checkout holding that module:

BACKPLANES_PROTO_REPO=/path/to/checkout ./scripts/gen-proto.sh

Plugin versions are pinned in buf.gen.yaml so the output is reproducible. The protocolbuffers/python plugin decides the gencode version stamped into every _pb2.py, and the protobuf runtime won't load gencode newer than itself. So if you bump that pin, bump the protobuf floor in pyproject.toml too. The script fails when the two disagree, which is better than an import error in someone else's app.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

backplanes-0.1.0.tar.gz (142.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

backplanes-0.1.0-py3-none-any.whl (118.8 kB view details)

Uploaded Python 3

File details

Details for the file backplanes-0.1.0.tar.gz.

File metadata

  • Download URL: backplanes-0.1.0.tar.gz
  • Upload date:
  • Size: 142.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.2 {"installer":{"name":"uv","version":"0.12.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for backplanes-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d907303e4b57f7ccd0885a023dfabbc6d798a58602b5052167f5405000f6af11
MD5 4c2a4c7b9ac3137b1fb3bf0c35a95442
BLAKE2b-256 57e8251b3c948f0534ff3bbeda4b5653b3f4825647f77469b2715f1545d2cac5

See more details on using hashes here.

File details

Details for the file backplanes-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: backplanes-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 118.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.2 {"installer":{"name":"uv","version":"0.12.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for backplanes-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a4bae54b70f1750fc71ab484a0cfc636c6e0e2c3c28e07d2dac72cb7a2b8f938
MD5 a8176eeca05bfcb988509e2b3e6ae922
BLAKE2b-256 4a289be3403517ed9c25dcd98ae1bd1100d692060584d4a5350f201c73b83233

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page