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.
- backplanes-0.1.0/LICENSE +49 -0
- backplanes-0.1.0/PKG-INFO +361 -0
- backplanes-0.1.0/README.md +328 -0
- backplanes-0.1.0/backplanes/__init__.py +95 -0
- backplanes-0.1.0/backplanes/_integration_events.py +97 -0
- backplanes-0.1.0/backplanes/_payload_fields.py +24 -0
- backplanes-0.1.0/backplanes/_stream_recording.py +548 -0
- backplanes-0.1.0/backplanes/_tool_calls.py +237 -0
- backplanes-0.1.0/backplanes/_web_integrations.py +353 -0
- backplanes-0.1.0/backplanes/audit.py +120 -0
- backplanes-0.1.0/backplanes/causality.py +61 -0
- backplanes-0.1.0/backplanes/client.py +1850 -0
- backplanes-0.1.0/backplanes/config.py +276 -0
- backplanes-0.1.0/backplanes/context.py +278 -0
- backplanes-0.1.0/backplanes/filesystem.py +35 -0
- backplanes-0.1.0/backplanes/identity.py +246 -0
- backplanes-0.1.0/backplanes/integrations.py +595 -0
- backplanes-0.1.0/backplanes/llm_telemetry.py +276 -0
- backplanes-0.1.0/backplanes/payloads.py +81 -0
- backplanes-0.1.0/backplanes/pii.py +118 -0
- backplanes-0.1.0/backplanes/py.typed +0 -0
- backplanes-0.1.0/backplanes/recording.py +115 -0
- backplanes-0.1.0/backplanes/runtime_identity.py +54 -0
- backplanes-0.1.0/backplanes/signing.py +113 -0
- backplanes-0.1.0/backplanes/tools.py +229 -0
- backplanes-0.1.0/backplanes/v1/__init__.py +0 -0
- backplanes-0.1.0/backplanes/v1/audit_pb2.py +58 -0
- backplanes-0.1.0/backplanes/v1/audit_pb2.pyi +465 -0
- backplanes-0.1.0/backplanes/v1/audit_pb2_grpc.py +4 -0
- backplanes-0.1.0/backplanes/v1/audit_pb2_grpc.pyi +17 -0
- backplanes-0.1.0/backplanes/v1/collector_pb2.py +106 -0
- backplanes-0.1.0/backplanes/v1/collector_pb2.pyi +905 -0
- backplanes-0.1.0/backplanes/v1/collector_pb2_grpc.py +776 -0
- backplanes-0.1.0/backplanes/v1/collector_pb2_grpc.pyi +491 -0
- backplanes-0.1.0/backplanes/v1/events_pb2.py +59 -0
- backplanes-0.1.0/backplanes/v1/events_pb2.pyi +416 -0
- backplanes-0.1.0/backplanes/v1/events_pb2_grpc.py +4 -0
- backplanes-0.1.0/backplanes/v1/events_pb2_grpc.pyi +17 -0
- backplanes-0.1.0/backplanes/v1/spotlight_pb2.py +55 -0
- backplanes-0.1.0/backplanes/v1/spotlight_pb2.pyi +299 -0
- backplanes-0.1.0/backplanes/v1/spotlight_pb2_grpc.py +4 -0
- backplanes-0.1.0/backplanes/v1/spotlight_pb2_grpc.pyi +17 -0
- backplanes-0.1.0/backplanes/wrappers.py +575 -0
- backplanes-0.1.0/backplanes.egg-info/PKG-INFO +361 -0
- backplanes-0.1.0/backplanes.egg-info/SOURCES.txt +62 -0
- backplanes-0.1.0/backplanes.egg-info/dependency_links.txt +1 -0
- backplanes-0.1.0/backplanes.egg-info/requires.txt +11 -0
- backplanes-0.1.0/backplanes.egg-info/top_level.txt +1 -0
- backplanes-0.1.0/pyproject.toml +92 -0
- backplanes-0.1.0/setup.cfg +4 -0
- backplanes-0.1.0/tests/test_audit.py +253 -0
- backplanes-0.1.0/tests/test_client_advanced.py +676 -0
- backplanes-0.1.0/tests/test_ergonomics.py +618 -0
- backplanes-0.1.0/tests/test_httpx_patch.py +584 -0
- backplanes-0.1.0/tests/test_llm_telemetry.py +301 -0
- backplanes-0.1.0/tests/test_pii_and_payloads.py +126 -0
- backplanes-0.1.0/tests/test_requests_patch.py +205 -0
- backplanes-0.1.0/tests/test_resilience.py +401 -0
- backplanes-0.1.0/tests/test_runtime_identity.py +27 -0
- backplanes-0.1.0/tests/test_signing.py +117 -0
- backplanes-0.1.0/tests/test_streaming.py +148 -0
- backplanes-0.1.0/tests/test_tool_calls.py +185 -0
- backplanes-0.1.0/tests/test_tools.py +241 -0
- backplanes-0.1.0/tests/test_wrappers.py +914 -0
backplanes-0.1.0/LICENSE
ADDED
|
@@ -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.
|