backplanes 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. backplanes-0.1.0/LICENSE +49 -0
  2. backplanes-0.1.0/PKG-INFO +361 -0
  3. backplanes-0.1.0/README.md +328 -0
  4. backplanes-0.1.0/backplanes/__init__.py +95 -0
  5. backplanes-0.1.0/backplanes/_integration_events.py +97 -0
  6. backplanes-0.1.0/backplanes/_payload_fields.py +24 -0
  7. backplanes-0.1.0/backplanes/_stream_recording.py +548 -0
  8. backplanes-0.1.0/backplanes/_tool_calls.py +237 -0
  9. backplanes-0.1.0/backplanes/_web_integrations.py +353 -0
  10. backplanes-0.1.0/backplanes/audit.py +120 -0
  11. backplanes-0.1.0/backplanes/causality.py +61 -0
  12. backplanes-0.1.0/backplanes/client.py +1850 -0
  13. backplanes-0.1.0/backplanes/config.py +276 -0
  14. backplanes-0.1.0/backplanes/context.py +278 -0
  15. backplanes-0.1.0/backplanes/filesystem.py +35 -0
  16. backplanes-0.1.0/backplanes/identity.py +246 -0
  17. backplanes-0.1.0/backplanes/integrations.py +595 -0
  18. backplanes-0.1.0/backplanes/llm_telemetry.py +276 -0
  19. backplanes-0.1.0/backplanes/payloads.py +81 -0
  20. backplanes-0.1.0/backplanes/pii.py +118 -0
  21. backplanes-0.1.0/backplanes/py.typed +0 -0
  22. backplanes-0.1.0/backplanes/recording.py +115 -0
  23. backplanes-0.1.0/backplanes/runtime_identity.py +54 -0
  24. backplanes-0.1.0/backplanes/signing.py +113 -0
  25. backplanes-0.1.0/backplanes/tools.py +229 -0
  26. backplanes-0.1.0/backplanes/v1/__init__.py +0 -0
  27. backplanes-0.1.0/backplanes/v1/audit_pb2.py +58 -0
  28. backplanes-0.1.0/backplanes/v1/audit_pb2.pyi +465 -0
  29. backplanes-0.1.0/backplanes/v1/audit_pb2_grpc.py +4 -0
  30. backplanes-0.1.0/backplanes/v1/audit_pb2_grpc.pyi +17 -0
  31. backplanes-0.1.0/backplanes/v1/collector_pb2.py +106 -0
  32. backplanes-0.1.0/backplanes/v1/collector_pb2.pyi +905 -0
  33. backplanes-0.1.0/backplanes/v1/collector_pb2_grpc.py +776 -0
  34. backplanes-0.1.0/backplanes/v1/collector_pb2_grpc.pyi +491 -0
  35. backplanes-0.1.0/backplanes/v1/events_pb2.py +59 -0
  36. backplanes-0.1.0/backplanes/v1/events_pb2.pyi +416 -0
  37. backplanes-0.1.0/backplanes/v1/events_pb2_grpc.py +4 -0
  38. backplanes-0.1.0/backplanes/v1/events_pb2_grpc.pyi +17 -0
  39. backplanes-0.1.0/backplanes/v1/spotlight_pb2.py +55 -0
  40. backplanes-0.1.0/backplanes/v1/spotlight_pb2.pyi +299 -0
  41. backplanes-0.1.0/backplanes/v1/spotlight_pb2_grpc.py +4 -0
  42. backplanes-0.1.0/backplanes/v1/spotlight_pb2_grpc.pyi +17 -0
  43. backplanes-0.1.0/backplanes/wrappers.py +575 -0
  44. backplanes-0.1.0/backplanes.egg-info/PKG-INFO +361 -0
  45. backplanes-0.1.0/backplanes.egg-info/SOURCES.txt +62 -0
  46. backplanes-0.1.0/backplanes.egg-info/dependency_links.txt +1 -0
  47. backplanes-0.1.0/backplanes.egg-info/requires.txt +11 -0
  48. backplanes-0.1.0/backplanes.egg-info/top_level.txt +1 -0
  49. backplanes-0.1.0/pyproject.toml +92 -0
  50. backplanes-0.1.0/setup.cfg +4 -0
  51. backplanes-0.1.0/tests/test_audit.py +253 -0
  52. backplanes-0.1.0/tests/test_client_advanced.py +676 -0
  53. backplanes-0.1.0/tests/test_ergonomics.py +618 -0
  54. backplanes-0.1.0/tests/test_httpx_patch.py +584 -0
  55. backplanes-0.1.0/tests/test_llm_telemetry.py +301 -0
  56. backplanes-0.1.0/tests/test_pii_and_payloads.py +126 -0
  57. backplanes-0.1.0/tests/test_requests_patch.py +205 -0
  58. backplanes-0.1.0/tests/test_resilience.py +401 -0
  59. backplanes-0.1.0/tests/test_runtime_identity.py +27 -0
  60. backplanes-0.1.0/tests/test_signing.py +117 -0
  61. backplanes-0.1.0/tests/test_streaming.py +148 -0
  62. backplanes-0.1.0/tests/test_tool_calls.py +185 -0
  63. backplanes-0.1.0/tests/test_tools.py +241 -0
  64. backplanes-0.1.0/tests/test_wrappers.py +914 -0
@@ -0,0 +1,49 @@
1
+ BACKPLANES PROPRIETARY LICENSE
2
+ All Rights Reserved
3
+
4
+ Copyright (c) 2026 Backplanes, Inc. All rights reserved.
5
+
6
+ This software and all related materials (the "Software") are proprietary to
7
+ Backplanes, Inc. ("Backplanes") and are protected by copyright and other laws.
8
+ No right, title, or interest in or to the Software is granted to any other
9
+ party by this license.
10
+
11
+ 1. No License Grant.
12
+ Except as expressly permitted in a separate written agreement with Backplanes,
13
+ you may not access, use, copy, modify, distribute, sublicense, sell, rent,
14
+ lease, loan, reverse engineer, decompile, disassemble, or create derivative
15
+ works of the Software. You may not make the Software available to any third
16
+ party.
17
+
18
+ 2. Internal Use Only.
19
+ The Software is intended solely for Backplanes' internal operation and
20
+ hosting. It is not licensed for external use, distribution, or deployment
21
+ by customers or third parties.
22
+
23
+ 3. Confidentiality.
24
+ The Software is confidential and proprietary. You must protect it using the
25
+ same degree of care you use to protect your own confidential information,
26
+ and in no event less than reasonable care.
27
+
28
+ 4. No Warranties.
29
+ THE SOFTWARE IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTIES OR
30
+ CONDITIONS OF ANY KIND, WHETHER EXPRESS, IMPLIED, OR STATUTORY, INCLUDING
31
+ WITHOUT LIMITATION ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
32
+ PARTICULAR PURPOSE, TITLE, OR NON-INFRINGEMENT. BACKPLANES DOES NOT WARRANT
33
+ THAT THE SOFTWARE WILL BE UNINTERRUPTED OR ERROR-FREE.
34
+
35
+ 5. Limitation of Liability.
36
+ TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL BACKPLANES BE
37
+ LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR EXEMPLARY
38
+ DAMAGES, OR FOR ANY LOSS OF PROFITS, REVENUE, DATA, OR USE, ARISING OUT OF
39
+ OR RELATED TO THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
40
+ DAMAGES. BACKPLANES' TOTAL LIABILITY FOR ANY CLAIM SHALL NOT EXCEED $100.
41
+
42
+ 6. Termination.
43
+ Any unauthorized use of the Software immediately terminates any permissions
44
+ you may have to use it. Upon termination, you must cease all use and destroy
45
+ all copies.
46
+
47
+ 7. Governing Law.
48
+ This license is governed by the laws of the United States of America, without regard to
49
+ conflict of laws principles.
@@ -0,0 +1,361 @@
1
+ Metadata-Version: 2.4
2
+ Name: backplanes
3
+ Version: 0.1.0
4
+ Summary: Backplanes Python SDK for agent telemetry and identity management.
5
+ License-Expression: LicenseRef-Proprietary
6
+ Project-URL: Homepage, https://backplanes.com
7
+ Project-URL: Repository, https://github.com/Backplanes/python-sdk
8
+ Project-URL: Issues, https://github.com/Backplanes/python-sdk/issues
9
+ Keywords: backplanes,telemetry,agents,observability
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: grpcio>=1.76.0
23
+ Requires-Dist: protobuf>=7.35.1
24
+ Requires-Dist: cryptography>=41.0.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: fastapi>=0.115.0; extra == "dev"
27
+ Requires-Dist: httpx>=0.27.0; extra == "dev"
28
+ Requires-Dist: pyright>=1.1.411; extra == "dev"
29
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
30
+ Requires-Dist: requests>=2.32.0; extra == "dev"
31
+ Requires-Dist: ruff>=0.9.0; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # Backplanes Python SDK
35
+
36
+ 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.
37
+
38
+ ## Table Of Contents
39
+
40
+ - [Install](#install)
41
+ - [Quick Start](#quick-start)
42
+ - [How Instrumentation Works](#how-instrumentation-works)
43
+ - [Modes](#modes)
44
+ - [Configuration And Identity](#configuration-and-identity)
45
+ - [Running Alongside Your Application](#running-alongside-your-application)
46
+ - [Custom Events](#custom-events)
47
+ - [What The SDK Covers](#what-the-sdk-covers)
48
+ - [Examples](#examples)
49
+ - [Docs](#docs)
50
+ - [Development](#development)
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ pip install backplanes
56
+ ```
57
+
58
+ For local development:
59
+
60
+ ```bash
61
+ uv pip install -e .
62
+ ```
63
+
64
+ ## Quick Start
65
+
66
+ 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.)
67
+
68
+ ```bash
69
+ export BACKPLANES_ORG_API_KEY="OK_..." # alternative to the key file
70
+ ```
71
+
72
+ Then instrument your agent with one call:
73
+
74
+ ```python
75
+ import anthropic
76
+ import backplanes
77
+
78
+ backplanes.init(agent_name="my-agent")
79
+
80
+ sdk = anthropic.Anthropic()
81
+ response = sdk.messages.create(
82
+ model="claude-haiku-4-5-20251001",
83
+ max_tokens=50,
84
+ messages=[{"role": "user", "content": "Say hello in five words."}],
85
+ )
86
+ ```
87
+
88
+ 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.
89
+
90
+ `init()` returns a `BackplanesClient` for everything beyond automatic capture — starting workflows, custom events, context propagation:
91
+
92
+ ```python
93
+ client = backplanes.init(agent_name="my-agent")
94
+ workflow_id = client.start_workflow()
95
+ ```
96
+
97
+ ## How Instrumentation Works
98
+
99
+ Instrumentation is three composable layers, highest fidelity first. See [docs/design/INSTRUMENTATION_TIERS.md](docs/design/INSTRUMENTATION_TIERS.md) for the full design.
100
+
101
+ **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:
102
+
103
+ ```python
104
+ import anthropic
105
+ import backplanes
106
+
107
+ client = backplanes.init(agent_name="my-agent", auto_instrument=False)
108
+ sdk = backplanes.wrap_anthropic(anthropic.Anthropic(), client)
109
+ ```
110
+
111
+ **`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.
112
+
113
+ **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.
114
+
115
+ 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.
116
+
117
+ ## Modes
118
+
119
+ ### Workflow Root
120
+
121
+ Use workflow root mode when your agent starts workflows. This needs an org API
122
+ key, passed directly or read from `BACKPLANES_ORG_API_KEY`.
123
+
124
+ ```python
125
+ client = backplanes.init(
126
+ org_api_key="OK_...",
127
+ agent_name="my-agent",
128
+ )
129
+ ```
130
+
131
+ A key file bundles the same org API key with an Ed25519 signing key, and the
132
+ client reads both from it:
133
+
134
+ ```python
135
+ client = backplanes.init(
136
+ config_file="backplanes-key.json",
137
+ agent_name="my-agent",
138
+ )
139
+ ```
140
+
141
+ Without an explicit `config_file`, the client searches `BACKPLANES_KEY_FILE`, `./backplanes-key.json`, and `~/.backplanes/backplanes-key.json` — see [docs/CONFIGURATION.md](docs/CONFIGURATION.md).
142
+
143
+ ### Propagating To Other Agents
144
+
145
+ Handing a workflow to a second agent means signing a `Backplanes-Context`, and
146
+ that needs the signing key from a key file. An org API key on its own won't do
147
+ it. `can_propagate_context` tells you which you have. Without a signing key each
148
+ agent's events still carry their own workflow ID, they just aren't linked into
149
+ one chain.
150
+
151
+ The `Backplanes-Context` header identifies your org, workflow, and agent chain,
152
+ and the transport patches send it to every destination:
153
+
154
+ ```python
155
+ backplanes.init(agent_name="my-agent")
156
+ ```
157
+
158
+ That is what lets a chain link without anyone describing the topology first, and
159
+ it has a disclosure consequence worth being explicit about: the same process
160
+ usually calls both your own services and third-party APIs, and those third
161
+ parties receive the header too.
162
+
163
+ ### Mid-Chain Agent
164
+
165
+ Use mid-chain mode when your agent receives a `Backplanes-Context` header from an upstream agent:
166
+
167
+ ```python
168
+ client = backplanes.init(agent_name="worker-agent")
169
+
170
+ context_jwt = request.headers.get("Backplanes-Context")
171
+ with client.request_context(context_jwt):
172
+ ... # work in this scope is attributed to the upstream workflow
173
+ ```
174
+
175
+ ### Local Development
176
+
177
+ By default the client targets `api.backplanes.com:443` with TLS enabled. To point the same code at a different collector, use the environment:
178
+
179
+ ```bash
180
+ export BACKPLANES_COLLECTOR_HOST=localhost
181
+ export BACKPLANES_COLLECTOR_PORT=50051
182
+ ```
183
+
184
+ For a collector behind a private certificate authority, keep TLS on and point
185
+ `BACKPLANES_TLS_CA_FILE` at the CA bundle — gRPC does not read the OS trust
186
+ store. The same settings are available as constructor parameters
187
+ (`collector_host`, `collector_port`, `tls_ca_cert`); precedence is
188
+ explicit parameter > key file > environment > default.
189
+
190
+ ### Claiming And Unclaimed Mode
191
+
192
+ A first run without org credentials registers the agent and logs a claim URL.
193
+ Claim it in the dashboard to link the agent to your organization; the client
194
+ picks the claim up on its next call and starts sending. With a key file or
195
+ `BACKPLANES_ORG_API_KEY` in place, the agent is active immediately and no
196
+ claiming step is needed.
197
+
198
+ Until an agent is claimed:
199
+
200
+ - `start_workflow()` returns a placeholder workflow ID
201
+ - `get_context_header()` returns `{}` because there is no active signed workflow context
202
+ - `ingest()` returns a stub response instead of sending events
203
+
204
+ An agent leaves unclaimed mode two ways:
205
+
206
+ - 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.
207
+ - 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.
208
+
209
+ ## Configuration And Identity
210
+
211
+ Every collector setting resolves from constructor parameters, the key file, and
212
+ the environment, in that order. Key files only override the collector settings
213
+ they actually specify. Named agents get their own identity file
214
+ (`~/.backplanes/identities/<agent_name>.json`), so several agents on one
215
+ machine stay distinct identities. The full environment variable table, key file
216
+ schema, and identity file locations are in
217
+ [docs/CONFIGURATION.md](docs/CONFIGURATION.md).
218
+
219
+ ## Running Alongside Your Application
220
+
221
+ If the collector has problems, your application should keep working.
222
+
223
+ - **Construction does not raise by default.** When the collector is unreachable the client
224
+ logs a warning and starts up with telemetry off, retrying registration in the
225
+ background. Pass `strict=True` if you would rather it raise
226
+ `BackplanesNotReadyError` — useful in CI, where silent telemetry is worse
227
+ than a failed build.
228
+ - **`is_ready` answers "would an event reach the collector".** It is False while
229
+ the agent is waiting to be claimed, which is where every new agent starts.
230
+ `has_identity` is the narrower question of whether registration succeeded.
231
+ An unclaimed client warns, so this state is visible without checking.
232
+ - **Every call has a timeout**, 10 seconds by default and configurable. A host
233
+ that drops packets silently will fail the call instead of hanging your
234
+ thread.
235
+ - **Ingest never blocks you.** `ingest()` starts the call and hands back the
236
+ future for it. gRPC does the network work on its own threads. Ask for the
237
+ answer only when you want it:
238
+
239
+ ```python
240
+ client.ingest(event) # fire and forget
241
+ client.ingest(event).result() # wait for the collector's counts
242
+ ```
243
+
244
+ At interpreter exit the client waits up to three seconds for whatever is
245
+ still in flight, so a short script usually doesn't need to do anything. That
246
+ wait is best effort and gives up quietly — if you need to know an event was
247
+ accepted, call `result()`. `flush(timeout=...)` waits for everything
248
+ outstanding and returns False if it ran out of time, which is what you want
249
+ before returning from a serverless handler. A call nobody waits on logs its
250
+ failure, since there's nobody to raise to.
251
+
252
+ ## Custom Events
253
+
254
+ Automatic instrumentation covers LLM and HTTP traffic. For everything else —
255
+ tool invocations, internal pipeline stages, domain-specific events —
256
+ `create_event(...)` and `ingest(...)` are still there:
257
+
258
+ ```python
259
+ client = backplanes.init(agent_name="my-agent")
260
+
261
+ event = client.create_event(
262
+ edge_type="tool",
263
+ direction="egress",
264
+ status="ok",
265
+ metadata={"tool.name": "send_email"},
266
+ )
267
+ client.ingest(event)
268
+ ```
269
+
270
+ If your agent is handling an incoming Backplanes workflow, wrap the work in
271
+ `request_context(...)` so events are attributed to the upstream chain:
272
+
273
+ ```python
274
+ context_jwt = request.headers["Backplanes-Context"]
275
+ with client.request_context(context_jwt):
276
+ client.ingest(client.create_event(edge_type="tool", direction="ingress"))
277
+ ```
278
+
279
+ `create_event(...)` supports the full event schema — sessions, instance IDs,
280
+ parent events, PII fields, token usage, error info, custom metadata. See
281
+ [docs/ADVANCED.md](docs/ADVANCED.md), and
282
+ [`examples/email_campaign`](examples/email_campaign) for tool and PII events in
283
+ a real workflow.
284
+
285
+ ## What The SDK Covers
286
+
287
+ - `init(...)` — one-call setup: constructs the client, hooks the Anthropic/OpenAI SDK constructors, installs the transport net
288
+ - `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`
289
+ - `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
290
+ - `patch_requests(...)` — the same net for outgoing `requests` calls
291
+ - `instrument_fastapi(...)`, `instrument_asgi(...)`, and `instrument_wsgi(...)` — one-line framework setup for inbound context and ingress telemetry (Django is WSGI/ASGI)
292
+ - `request_context(...)` — request-scoped propagation in web apps and workers
293
+ - `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
294
+ - `create_batch(...)`, `ingest_batch(...)`, and `ingest_stream(...)` — explicit batch construction, unary batch ingest, and the collector's client-streaming RPC
295
+
296
+ Both HTTP patches record calls to hosts you don't own against an ID derived
297
+ from the hostname, so you can see which third party received the data. That is
298
+ unconditional — it needs no argument, and sends nothing to the third party.
299
+
300
+ The `Backplanes-Context` header is also unconditional: every destination gets
301
+ the org, workflow, and agent chain while a workflow context is active. That
302
+ includes third-party APIs, so enable the transport patches only where that
303
+ disclosure is acceptable:
304
+
305
+ ```python
306
+ patch_httpx(client)
307
+ patch_requests(client)
308
+ ```
309
+
310
+ You can also import the generated proto modules directly:
311
+
312
+ ```python
313
+ from backplanes.v1 import collector_pb2, events_pb2
314
+ ```
315
+
316
+ ## Examples
317
+
318
+ Runnable, real-world examples — from a five-line quickstart to a multi-agent
319
+ workflow with context propagation — live in [`examples/`](examples), each with
320
+ its own README.
321
+
322
+ ## Docs
323
+
324
+ Focused docs live in [`docs/`](docs):
325
+
326
+ - [Configuration And Identity](docs/CONFIGURATION.md)
327
+ - [Advanced Usage](docs/ADVANCED.md)
328
+ - [Integrations](docs/INTEGRATIONS.md)
329
+ - [Framework And App Placement](docs/FRAMEWORKS.md)
330
+ - [Context Model](docs/CONTEXT_MODEL.md)
331
+ - [FastAPI And ASGI Apps](docs/FASTAPI.md)
332
+ - [Django And WSGI/ASGI Apps](docs/DJANGO.md)
333
+ - [A2A / A2P / MCP / Worker Patterns](docs/A2A_A2P.md)
334
+ - [Releasing](docs/RELEASING.md)
335
+
336
+ ## Development
337
+
338
+ ```bash
339
+ uv sync
340
+ uv build
341
+ uv run ruff check .
342
+ uv run pytest
343
+ ```
344
+
345
+ ### Regenerating Protobuf Bindings
346
+
347
+ `backplanes/v1/` is generated from the Backplanes `proto/` buf module and
348
+ checked in, so installing this package needs neither `buf` nor `protoc`. To pick
349
+ up proto changes, point `BACKPLANES_PROTO_REPO` at a checkout holding that
350
+ module:
351
+
352
+ ```bash
353
+ BACKPLANES_PROTO_REPO=/path/to/checkout ./scripts/gen-proto.sh
354
+ ```
355
+
356
+ Plugin versions are pinned in [`buf.gen.yaml`](buf.gen.yaml) so the output is
357
+ reproducible. The `protocolbuffers/python` plugin decides the gencode version
358
+ stamped into every `_pb2.py`, and the protobuf runtime won't load gencode newer
359
+ than itself. So if you bump that pin, bump the `protobuf` floor in
360
+ `pyproject.toml` too. The script fails when the two disagree, which is better
361
+ than an import error in someone else's app.