You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 77a7cf5
Browse filesBrowse the repository at this point in the historyBrowse files
feat(client)!: capture conversation content by default (#213)
## Summary
`SDKOptions.capture_content` now defaults to `true`. Online handler
spans and offline evaluation spans (from #170, now merged) use that same
gate. An explicit `false` on `configure`, a handler factory, or a
runner-written span still strips prompts, output, tool payloads, and
judge explanations, including on offline evaluation spans. The
`$ld:ai:offline-evals:*` events still carry no transcript.
Marked breaking (`!`): a process that never calls `configure` now sends
conversation text to its collector. Without the marker,
`bump-patch-for-minor-pre-major` would ship this as a patch release.
Also corrects `TELEMETRY-CONTRACT.md` section 4a. It said
`gen_ai.evaluation.explanation` is never emitted, but the judge path
writes it whenever the judge handler captures content. With capture on
by default, the explanation is now on judge spans by default.
Rebased onto `main` after #170 was squash-merged, so the diff is only
the default change.
## Test plan
- [x] `uv run pytest`: 3644 passed, 18 skipped
- [x] `uv run mypy packages/*/src`: 107 source files, no issues
- [x] `uv run ruff check .` and `uv run ruff format --check .`: clean
- [x] `make api-report-check`: 14 API reports are current
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> <sup>[Cursor Bugbot](https://cursor.com/bugbot) is generating a
summary for commit 435e4cc. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Summary
`SDKOptions.capture_content` now defaults to `true`. Online handler
spans and offline evaluation spans (from #170, now merged) use that same
gate. An explicit `false` on `configure`, a handler factory, or a
runner-written span still strips prompts, output, tool payloads, and
judge explanations, including on offline evaluation spans. The
`$ld:ai:offline-evals:*` events still carry no transcript.
Marked breaking (`!`): a process that never calls `configure` now sends
conversation text to its collector. Without the marker,
`bump-patch-for-minor-pre-major` would ship this as a patch release.
Also corrects `TELEMETRY-CONTRACT.md` section 4a. It said
`gen_ai.evaluation.explanation` is never emitted, but the judge path
writes it whenever the judge handler captures content. With capture on
by default, the explanation is now on judge spans by default.
Rebased onto `main` after #170 was squash-merged, so the diff is only
the default change.
## Decision
Opt-out, for online and offline. In [this
thread](https://launchdarkly.slack.com/archives/C0B9D3510TG/p1792783332999349?thread_ts=1792695162.107739&cid=C0B9D3510TG),
Malachi said the current AI terms cover moving the SDK updates forward
and to set this up as opt-out rather than opt-in. Ben then asked to flip
the default to on.
## Test plan
- [x] `uv run pytest`: 3644 passed, 18 skipped
- [x] `uv run pytest packages/client/tests` after the content-module
docstring fix: 1904 passed
- [x] `uv run mypy packages/*/src`: 107 source files, no issues
- [x] `uv run ruff check .` and `uv run ruff format --check .`: clean
- [x] `make api-report-check`: 14 API reports are current
<!-- CURSOR_AGENT_PR_BODY_END -->
<div><a
href="https://cursor.com/agents/bc-4232ef78-2eeb-54ed-ae75-6629724917b3?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a
href="https://cursor.com/background-agent?bcId=bc-4232ef78-2eeb-54ed-ae75-6629724917b3&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Copy file name to clipboardExpand all lines: AGENTS.md
+8-7Lines changed: 8 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -156,7 +156,7 @@ The client manages a singleton connection to LaunchDarkly and the associated tel
156
156
|---|---|
157
157
|`init_client(options?)`| Auto-discovers and initializes `launchdarkly-server-sdk` (optional dep, loaded via `importlib`). Optional — the first AI API call triggers lazy init when `LD_SDK_KEY` is set. Accepts optional overrides for SDK key, base URIs, service name, environment, and OTLP endpoint. Returns `Awaitable[LDClientInterface]`. On every successful path, including the already-initialized path, flushes `$ld:ai:sdk:info` for any LaunchDarkly AI packages that have not yet reported. Does not take behavior defaults. |
158
158
|`init_client(client=...)`|**BYOC overload** — accepts a pre-initialized `LDClientInterface`. Stores it directly without calling the SDK. Flushes pending `$ld:ai:sdk:info` events. |
159
-
|`configure(*, options)`| Sets process-wide behavior defaults on the lifecycle singleton. `options` is a frozen `SDKOptions` (`capture_content: bool = False`). Keyword-only. Handlers read it at invocation. An explicit factory or wrapper `capture_content` overrides it. Replaceable until the first AI invocation; an identical value is allowed after. Not read from request data. |
159
+
|`configure(*, options)`| Sets process-wide behavior defaults on the lifecycle singleton. `options` is a frozen `SDKOptions` (`capture_content: bool = True`). Keyword-only. Handlers read it at invocation. An explicit factory or wrapper `capture_content` overrides it, and an explicit `False` strips content everywhere, including offline evaluation spans. Replaceable until the first AI invocation; an identical value is allowed after. Not read from request data. |
160
160
|`get_client()`| Returns the initialized `LDClientInterface`. Throws if initialization has not completed. |
161
161
|`shutdown()`| Flushes all pending events and telemetry, then closes the client. Must be awaited before the process exits. Clears sdk-info reporting so a later client reports again. Restores the built-in SDK defaults. |
162
162
|`register_ai_sdk_package(name, version)`| Records a LaunchDarkly AI package identity. Handler and convenience packages call this at import time. |
@@ -571,14 +571,15 @@ beside the input count, so its handlers add it in. OpenAI and LangChain already
571
571
input, so theirs pass the figure through. Centralising that rule would double-count for two
572
572
providers out of three. `SpanUsage` is the type that means the folding is already done.
573
573
574
-
#### Content is off by default
574
+
#### Content is on by default
575
575
576
576
Every factory takes `capture_content: bool | None = None`. `None` reads `configure()` at
577
-
invocation, which is off until a process sets `SDKOptions(capture_content=True)`. An explicit
578
-
`True` or `False` overrides that default. Guard at the call site as well as inside the helper: the
579
-
helper's guard makes a forgotten call site harmless, and the call site's guard avoids serialising
580
-
JSON that would then be discarded, once per turn, in a loop. Resolve the bool when the handler
581
-
runs, not when it is constructed.
577
+
invocation. The built-in default is on until a process sets `SDKOptions(capture_content=False)`.
578
+
An explicit `True` or `False` overrides that default, and an explicit `False` strips content
579
+
everywhere, including offline evaluation spans. Guard at the call site as well as inside the
580
+
helper: the helper's guard makes a forgotten call site harmless, and the call site's guard avoids
581
+
serialising JSON that would then be discarded, once per turn, in a loop. Resolve the bool when the
582
+
handler runs, not when it is constructed.
582
583
583
584
#### Finish reasons have three mechanisms, not one
Copy file name to clipboardExpand all lines: README.md
+9-6Lines changed: 9 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -542,20 +542,23 @@ This matters most on Anthropic, which reports cache reads and writes beside the
542
542
than inside it. A turn that reads 19,971 tokens from cache and writes 3,580 more reports an input of
543
543
3 from the provider; the span reports 23,554.
544
544
545
-
### Conversation content is off by default
545
+
### Conversation content is on by default
546
546
547
-
Prompts, model output, tool arguments and tool results are personal data, so a span carries only
548
-
metadata unless you ask for more: models, token counts, timings, tool names.
547
+
Prompts, model output, tool arguments and tool results are personal data. The built-in default
548
+
sends that text. A span carries only metadata — models, token counts, timings, tool names — when
549
+
capture is turned off.
549
550
550
-
Pass `capture_content=True` to a handler factory to include the conversation, or turn it on for every handler from process startup. See [Process-wide defaults](packages/client/README.md#process-wide-defaults).
551
+
Pass `capture_content=False` to a handler factory to leave the conversation off that handler, or
552
+
turn it off for every handler from process startup. An explicit `False` wins everywhere, including
553
+
on offline evaluation spans. See [Process-wide defaults](packages/client/README.md#process-wide-defaults).
551
554
552
555
```python
553
556
from launchdarkly_ai_claude_messages import create_claude_messages_handler
Copy file name to clipboardExpand all lines: packages/claude-agents/README.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -40,7 +40,7 @@ asyncio.run(main())
40
40
41
41
| Option | Default | Description |
42
42
|---|---|---|
43
-
|`capture_content`|`None`| Put prompts, model output, and tool arguments and results on the emitted spans. `None` follows `configure()`, which is off by default. |
43
+
|`capture_content`|`None`| Put prompts, model output, and tool arguments and results on the emitted spans. `None` follows `configure()`, which is on by default. An explicit `False` leaves them off. |
44
44
|`cwd`| the process's working directory | Working directory for the Claude Code process the Agent SDK starts. A `str` or path-like. |
45
45
|`setting_sources`|`[]` (none) | Which Claude Code settings sources to load: `"user"` (`~/.claude/settings.json`), `"project"` (`.claude/settings.json`, and `CLAUDE.md`), `"local"` (`.claude/settings.local.json`). |
Copy file name to clipboardExpand all lines: packages/claude-agents/agents.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
> **Span shape.** This package emits `invoke_agent` → `chat {model}` → `execute_tool {name}`, with
4
4
> tool spans as siblings of `chat`. Span construction lives in `spans.py` beside the handler.
5
-
> Conversation content is off unless the caller passes `capture_content=True`, or the process sets it. See [Process-wide defaults](../client/README.md#process-wide-defaults).
5
+
> Conversation content is on unless the caller passes `capture_content=False`, or the process turns it off. See [Process-wide defaults](../client/README.md#process-wide-defaults).
6
6
> `TELEMETRY-CONTRACT.md` at the repo root is the authority; read it before changing span code.
Copy file name to clipboardExpand all lines: packages/claude-messages/agents.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
> **Span shape.** This package emits `invoke_agent` → `chat {model}` → `execute_tool {name}`, with
4
4
> tool spans as siblings of `chat`. Span construction lives in `spans.py` beside the handler.
5
-
> Conversation content is off unless the caller passes `capture_content=True`, or the process sets it. See [Process-wide defaults](../client/README.md#process-wide-defaults).
5
+
> Conversation content is on unless the caller passes `capture_content=False`, or the process turns it off. See [Process-wide defaults](../client/README.md#process-wide-defaults).
6
6
> `TELEMETRY-CONTRACT.md` at the repo root is the authority; read it before changing span code.
0 commit comments