@sentrial/sdk 0.4.5 → 0.6.0
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.
- package/README.md +357 -41
- package/dist/cli.cjs +6199 -107
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +4 -46
- package/dist/cli.d.ts +4 -46
- package/dist/cli.js +6260 -163
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +4955 -1060
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +949 -527
- package/dist/index.d.ts +949 -527
- package/dist/index.js +4939 -1060
- package/dist/index.js.map +1 -1
- package/dist/types-BEf8OATP.d.cts +612 -0
- package/dist/types-BEf8OATP.d.ts +612 -0
- package/package.json +23 -12
package/README.md
CHANGED
|
@@ -19,35 +19,92 @@ pnpm add @sentrial/sdk
|
|
|
19
19
|
yarn add @sentrial/sdk
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
## Breaking changes
|
|
23
|
+
|
|
24
|
+
**0.5.0**
|
|
25
|
+
|
|
26
|
+
- **Transport is now OpenTelemetry (OTLP).** Telemetry ships over `/api/otel/v1/traces`; this SDK no
|
|
27
|
+
longer POSTs sessions/events to the legacy `/api/sdk/*` endpoints (those endpoints remain available
|
|
28
|
+
server-side as thin compatibility adapters that translate onto the OTLP ingest pipeline, so older SDK
|
|
29
|
+
releases keep working — new integrations use OTLP; the Python sync and async clients both emit OTLP
|
|
30
|
+
too). The OTLP ingest route requires the API
|
|
31
|
+
key to carry **both `sessions:write` and `events:write`** scopes — dashboard-generated keys include
|
|
32
|
+
both, but a manually-scoped `sessions:write`-only key will silently drop data. Add `events:write`
|
|
33
|
+
to the key (see [Transport](#transport)).
|
|
34
|
+
- **Timestamp fields are now `string` (ISO 8601), not `Date`.** The `Session` fields (`createdAt` /
|
|
35
|
+
`updatedAt` / `completedAt`) and `Event.timestamp` were retyped because the API returns JSON and
|
|
36
|
+
`response.json()` never revived them into `Date` objects, so the old `Date` typing was incorrect —
|
|
37
|
+
but this is a compile-breaking change for code like `session.createdAt.getTime()`. Migrate by
|
|
38
|
+
parsing where needed: `new Date(session.createdAt).getTime()`.
|
|
39
|
+
- **`PiiConfig.enhancedDetection` was removed.** The field was a no-op (never read by redaction
|
|
40
|
+
logic) and is gone from `PiiConfig`, mirroring the Python SDK. Drop it from any `PiiConfig`
|
|
41
|
+
literal — it had no effect.
|
|
42
|
+
|
|
43
|
+
Shipped under a minor bump (not a patch), per the SDK compatibility policy.
|
|
44
|
+
|
|
22
45
|
## CLI
|
|
23
46
|
|
|
24
47
|
The SDK package also ships a `sentrial` binary for scriptable workflows. This is the
|
|
25
48
|
first packaging step toward a dedicated `npm install sentrial` style CLI package.
|
|
26
49
|
|
|
27
50
|
```bash
|
|
28
|
-
#
|
|
29
|
-
|
|
51
|
+
# Install globally
|
|
52
|
+
npm install -g @sentrial/sdk
|
|
53
|
+
|
|
54
|
+
# Or run without installing
|
|
55
|
+
npx -p @sentrial/sdk sentrial sessions list
|
|
30
56
|
|
|
31
57
|
# Save credentials locally
|
|
32
|
-
|
|
58
|
+
sentrial login --api-key sentrial_live_xxx
|
|
33
59
|
|
|
34
60
|
# Install the SDK package and scaffold a safe example integration
|
|
35
|
-
|
|
61
|
+
sentrial install
|
|
36
62
|
|
|
37
63
|
# Or scaffold only, without installing packages
|
|
38
|
-
|
|
64
|
+
sentrial init
|
|
39
65
|
|
|
40
66
|
# Inspect production runs
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
67
|
+
sentrial sessions list --last 7d --status failed --agent support
|
|
68
|
+
sentrial sessions get <session-id>
|
|
69
|
+
sentrial events list <session-id> --type tool_call
|
|
44
70
|
|
|
45
71
|
# Review signals and run CI gates
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
72
|
+
sentrial signals list --type negative --last 30d
|
|
73
|
+
sentrial diagnose <session-id>
|
|
74
|
+
sentrial evaluate <experiment-id> --min-score 80
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
> Note: `sentrial evaluate` calls `/api/v1/evaluate`, which requires an API key
|
|
78
|
+
> scoped with `metrics:read`. Make sure the key you use (via `--api-key`,
|
|
79
|
+
> `SENTRIAL_API_KEY`, or `sentrial login`) carries that scope, or the CI gate
|
|
80
|
+
> will fail with a 403.
|
|
81
|
+
|
|
82
|
+
### Guided setup and delivery checks
|
|
83
|
+
|
|
84
|
+
Setup and Doctor require `@sentrial/sdk` 0.6.0 or later. Start in your agent project:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npx -p @sentrial/sdk@0.6.0 sentrial setup --dry-run --json
|
|
88
|
+
npx -p @sentrial/sdk@0.6.0 sentrial setup
|
|
89
|
+
# Coding agents / CI: set SENTRIAL_API_KEY privately first.
|
|
90
|
+
npx -p @sentrial/sdk@0.6.0 sentrial setup --yes --non-interactive --json
|
|
91
|
+
npx -p @sentrial/sdk@0.6.0 sentrial doctor --workspace <organization-uuid> --json
|
|
92
|
+
npx -p @sentrial/sdk@0.6.0 sentrial doctor --session <session-uuid> --json
|
|
49
93
|
```
|
|
50
94
|
|
|
95
|
+
Setup verifies workspace read access, inspects installed dependencies and creates an integration
|
|
96
|
+
example while preserving existing files. A prompted key is hidden and is not saved. It does not
|
|
97
|
+
execute the example or call a model. Keep the key in your agent runtime, adapt the example to your
|
|
98
|
+
agent, run it, then use Doctor to inspect its session ID. Python-only projects can use
|
|
99
|
+
`python -m sentrial.cli setup` from Python SDK 0.8.0 without Node.
|
|
100
|
+
|
|
101
|
+
Doctor's `ready_for_instrumentation` result verifies read access, not delivery. `trace_verified`
|
|
102
|
+
means this completed trace's source-numbered events were read back without reported gaps or
|
|
103
|
+
inspection warnings. It does not establish business success, application-wide coverage or signal
|
|
104
|
+
accuracy. Session listing needs `sessions:read`; delivery readback also needs `events:read` and
|
|
105
|
+
an API deployment supporting `/api/v1/sessions/<id>/health`. Both reads share one bounded
|
|
106
|
+
`--timeout-ms` deadline (100–30000 ms). Redirects are refused; reports omit trace content and keys.
|
|
107
|
+
|
|
51
108
|
## Quick Start (30 seconds)
|
|
52
109
|
|
|
53
110
|
### Option 1: Auto-Wrap LLM Clients (Recommended)
|
|
@@ -95,11 +152,11 @@ const supportAgent = withSession('support-agent', async (userId: string, message
|
|
|
95
152
|
### Option 3: Vercel AI SDK
|
|
96
153
|
|
|
97
154
|
```typescript
|
|
98
|
-
import {
|
|
155
|
+
import { configureVercel, wrapAISDK } from '@sentrial/sdk';
|
|
99
156
|
import * as ai from 'ai';
|
|
100
157
|
import { openai } from '@ai-sdk/openai';
|
|
101
158
|
|
|
102
|
-
|
|
159
|
+
configureVercel({ apiKey: process.env.SENTRIAL_API_KEY });
|
|
103
160
|
|
|
104
161
|
const { generateText, streamText } = wrapAISDK(ai);
|
|
105
162
|
|
|
@@ -110,6 +167,59 @@ const { text } = await generateText({
|
|
|
110
167
|
});
|
|
111
168
|
```
|
|
112
169
|
|
|
170
|
+
### Option 4: OpenAI Agents SDK
|
|
171
|
+
|
|
172
|
+
One call registers a trace processor with the Agents SDK (`@openai/agents`, an optional peer
|
|
173
|
+
dependency, supported range `>=0.1.0 <1.0.0`) — every run becomes a Sentrial session with LLM
|
|
174
|
+
events, tool calls, handoffs, and guardrails. Don't combine with `wrapOpenAI()` on the same run
|
|
175
|
+
(LLM calls would be double-counted).
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { Agent, run, addTraceProcessor } from '@openai/agents';
|
|
179
|
+
import { sentrial, instrumentOpenAIAgents } from '@sentrial/sdk';
|
|
180
|
+
|
|
181
|
+
sentrial.configure({ apiKey: process.env.SENTRIAL_API_KEY });
|
|
182
|
+
instrumentOpenAIAgents({ addTraceProcessor }, { userId: 'user_123' });
|
|
183
|
+
|
|
184
|
+
const agent = new Agent({ name: 'Assistant', instructions: 'Be helpful.' });
|
|
185
|
+
const result = await run(agent, 'Write a haiku about recursion.');
|
|
186
|
+
// The run is now a Sentrial session with LLM events, tool calls, handoffs.
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
See the [OpenAI Agents SDK integration docs](https://www.sentrial.com/docs/integrations/openai-agents)
|
|
190
|
+
for configuration options, the run→session mapping, and batching/flush guidance.
|
|
191
|
+
|
|
192
|
+
### Option 5: Google ADK
|
|
193
|
+
|
|
194
|
+
Register one Plugin on your Google ADK `Runner` (`@google/adk`, an optional peer dependency,
|
|
195
|
+
supported range `>=0.1.0 <2.0.0`) — every run becomes a Sentrial session with LLM events, tool
|
|
196
|
+
calls, tokens, cost, and errors. The Plugin fires globally across every agent, tool, and model
|
|
197
|
+
call in the invocation. Don't combine with `wrapGoogle()` on the same run (LLM calls would be
|
|
198
|
+
double-counted).
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { InMemoryRunner, LlmAgent } from '@google/adk';
|
|
202
|
+
import { sentrial, SentrialAdkPlugin } from '@sentrial/sdk';
|
|
203
|
+
|
|
204
|
+
sentrial.configure({ apiKey: process.env.SENTRIAL_API_KEY });
|
|
205
|
+
|
|
206
|
+
const agent = new LlmAgent({ name: 'assistant', model: 'gemini-2.0-flash' });
|
|
207
|
+
const runner = new InMemoryRunner({ agent, plugins: [new SentrialAdkPlugin()] });
|
|
208
|
+
// Every runner.runAsync(...) is now a Sentrial session with LLM events, tool calls, and cost.
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Already constructed the `Runner`? Attach after the fact — `instrumentAdk()` is idempotent per
|
|
212
|
+
runner:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import { instrumentAdk } from '@sentrial/sdk';
|
|
216
|
+
|
|
217
|
+
instrumentAdk(runner, { userId: 'user_123' });
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
See the [Google ADK integration docs](https://www.sentrial.com/docs/integrations/google-adk)
|
|
221
|
+
for configuration options, the run→session mapping, and batching/flush guidance.
|
|
222
|
+
|
|
113
223
|
## Features
|
|
114
224
|
|
|
115
225
|
### LLM Auto-Wrappers
|
|
@@ -170,7 +280,7 @@ await experiment.run(async (testCase, variant, tracker) => {
|
|
|
170
280
|
|
|
171
281
|
### Vercel AI SDK Integration
|
|
172
282
|
|
|
173
|
-
Full support for Vercel AI SDK v3-
|
|
283
|
+
Full support for Vercel AI SDK v3-v7:
|
|
174
284
|
|
|
175
285
|
```typescript
|
|
176
286
|
import { wrapAISDK } from '@sentrial/sdk';
|
|
@@ -193,6 +303,8 @@ const { text } = await generateText({
|
|
|
193
303
|
});
|
|
194
304
|
```
|
|
195
305
|
|
|
306
|
+
> **Note:** `zod` is not a direct dependency of `@sentrial/sdk` — it comes transitively from the Vercel AI SDK (`ai`), which you install to use `wrapAISDK`. The `import { z } from 'zod'` above resolves because `ai` pulls `zod` in as a peer.
|
|
307
|
+
|
|
196
308
|
## Configuration
|
|
197
309
|
|
|
198
310
|
### Environment Variables
|
|
@@ -202,6 +314,45 @@ SENTRIAL_API_URL=https://api.sentrial.com
|
|
|
202
314
|
SENTRIAL_API_KEY=sentrial_live_xxx
|
|
203
315
|
```
|
|
204
316
|
|
|
317
|
+
### Optional agent reports
|
|
318
|
+
|
|
319
|
+
An agent can report missing context, a tool problem, a missing capability, or uncertain completion without marking the session failed:
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
await interaction.reportDiagnostic({
|
|
323
|
+
reportId: 'claim-context-1',
|
|
324
|
+
category: 'missing_context',
|
|
325
|
+
state: 'reported',
|
|
326
|
+
description: 'The policy number needed for this claim is missing.',
|
|
327
|
+
});
|
|
328
|
+
// Later, reuse the report ID and set state: 'recovered' with a new description.
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
The equivalent client method is `client.reportDiagnostic({ sessionId, report })`. Both a problem and its claimed recovery are agent assertions. They do not create a confirmed signal, training label, business outcome, or automatic action. Each call records a separate event; no prior report is overwritten. There are no model calls.
|
|
332
|
+
|
|
333
|
+
Use an opaque report ID (1–128 letters, digits, `.`, `_`, `:`, or `-`, starting with a letter/digit). Descriptions are limited to 4,096 UTF-8 bytes. Optional `relatedSpanIds` contains at most eight distinct, nonzero, lowercase 16-digit OTel span IDs from the current trace. These references are agent-supplied and are not verified or followed automatically. Normal metadata redaction, batching, transport retry and fail-silent behavior apply. A resolved call is not a durable-delivery receipt; use delivery diagnostics and inspect the retained event. Unknown/degraded/finished sessions cannot accept new reports.
|
|
334
|
+
|
|
335
|
+
The updated collector and trace UI display these as **Agent report**, including **Reported recovered**. Older collectors preserve generic event metadata but do not supply the validated report view. Existing signal evidence selectors do not consume reports as authoritative evidence; measuring incremental detection and review noise requires a separately reviewed evaluation.
|
|
336
|
+
|
|
337
|
+
### Transport
|
|
338
|
+
|
|
339
|
+
The SDK sends telemetry over **OpenTelemetry (OTLP)** to `/api/otel/v1/traces` — this is the sole
|
|
340
|
+
ingestion path for this SDK (it no longer POSTs sessions/events to the legacy `/api/sdk/*` endpoints,
|
|
341
|
+
which remain available server-side as thin compatibility adapters translated onto the OTLP ingest
|
|
342
|
+
pipeline so older SDK releases keep working; the Python sync and async clients both emit OTLP too).
|
|
343
|
+
The OTLP ingest route
|
|
344
|
+
requires the API key to carry **both `sessions:write` and `events:write`** scopes — keys
|
|
345
|
+
generated in the dashboard include both automatically. If you use a manually-scoped key that
|
|
346
|
+
only has `sessions:write`, session creation will 403 and tracking is silently dropped; add
|
|
347
|
+
`events:write` to the key.
|
|
348
|
+
|
|
349
|
+
Events and completion attach to an in-memory span the client holds from
|
|
350
|
+
`createSession()` / `begin()`, so they only apply to sessions **created on the same client
|
|
351
|
+
instance**. If you obtain a session id elsewhere — a session created out-of-band, shared across
|
|
352
|
+
processes, or tracked on a different `SentrialClient` — its events and finalization are skipped
|
|
353
|
+
(the SDK logs a one-time warning). Use the same client instance for the full begin→track→finish
|
|
354
|
+
flow.
|
|
355
|
+
|
|
205
356
|
### Programmatic
|
|
206
357
|
|
|
207
358
|
```typescript
|
|
@@ -284,23 +435,23 @@ const cost = calculateOpenAICost({
|
|
|
284
435
|
|
|
285
436
|
## Framework Compatibility
|
|
286
437
|
|
|
287
|
-
| Framework
|
|
288
|
-
|
|
|
289
|
-
| **Direct OpenAI**
|
|
290
|
-
| **Direct Anthropic**
|
|
291
|
-
| **Direct Gemini**
|
|
292
|
-
| **Vercel AI SDK**
|
|
293
|
-
| **
|
|
294
|
-
| **
|
|
295
|
-
| **
|
|
438
|
+
| Framework | Integration | Status |
|
|
439
|
+
| --------------------- | -------------------------- | ------ |
|
|
440
|
+
| **Direct OpenAI** | `wrapOpenAI()` | ✔️ |
|
|
441
|
+
| **Direct Anthropic** | `wrapAnthropic()` | ✔️ |
|
|
442
|
+
| **Direct Gemini** | `wrapGoogle()` | ✔️ |
|
|
443
|
+
| **Vercel AI SDK** | `wrapAISDK()` | ✔️ |
|
|
444
|
+
| **OpenAI Agents SDK** | `instrumentOpenAIAgents()` | ✔️ |
|
|
445
|
+
| **Google ADK** | `instrumentAdk()` | ✔️ |
|
|
446
|
+
| **Express/Fastify** | Decorators | ✔️ |
|
|
447
|
+
| **Next.js** | Decorators | ✔️ |
|
|
448
|
+
| **Custom agents** | Full API | ✔️ |
|
|
296
449
|
|
|
297
450
|
## Documentation
|
|
298
451
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
- `
|
|
302
|
-
- `docs/integrations/vercel-ai-sdk.md` - Vercel AI SDK integration
|
|
303
|
-
- `docs/features/experiments.md` - Experiments guide
|
|
452
|
+
- Full reference: [https://sentrial.com/docs](https://sentrial.com/docs)
|
|
453
|
+
- Repository README: the root `README.md` maps every SDK entry point and the ingest flow
|
|
454
|
+
- Framework wrappers live in `src/vercel.ts` (Vercel AI SDK), `src/openai-agents.ts` (OpenAI Agents SDK), `src/google-adk.ts` (Google ADK), `src/wrappers.ts` (OpenAI/Anthropic), and `src/decorators.ts`
|
|
304
455
|
|
|
305
456
|
## Support
|
|
306
457
|
|
|
@@ -313,20 +464,185 @@ See the `docs/` folder in the repository:
|
|
|
313
464
|
Both SDKs share the same core API (`begin`/`finish`, tool & LLM tracking, production
|
|
314
465
|
experiments, provider wrappers). Framework integrations currently differ:
|
|
315
466
|
|
|
316
|
-
| Capability | TypeScript
|
|
317
|
-
| ----------------------------------------------------- |
|
|
318
|
-
| Core tracking (begin/finish, tools, LLM, experiments) | ✓
|
|
319
|
-
| OpenAI / Anthropic / Google wrappers | ✓
|
|
320
|
-
| Vercel AI SDK helper | ✓
|
|
321
|
-
| Claude Agent SDK | partial (single-turn)
|
|
322
|
-
| OpenTelemetry export |
|
|
323
|
-
| Langfuse dual-write | —
|
|
324
|
-
| AutoGen / CrewAI / LangChain handlers | —
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
467
|
+
| Capability | TypeScript | Python |
|
|
468
|
+
| ----------------------------------------------------- | -------------------------- | ------------------- |
|
|
469
|
+
| Core tracking (begin/finish, tools, LLM, experiments) | ✓ | ✓ |
|
|
470
|
+
| OpenAI / Anthropic / Google wrappers | ✓ | ✓ |
|
|
471
|
+
| Vercel AI SDK helper | ✓ | — (JS-only) |
|
|
472
|
+
| Claude Agent SDK | partial (single-turn) | ✓ (full multi-turn) |
|
|
473
|
+
| OpenTelemetry export | ✓ (default OTLP transport) | ✓ |
|
|
474
|
+
| Langfuse dual-write | — | ✓ |
|
|
475
|
+
| AutoGen / CrewAI / LangChain handlers | — | ✓ |
|
|
476
|
+
| OpenAI Agents SDK trace processor | ✓ | ✓ |
|
|
477
|
+
| Google ADK Runner plugin | ✓ | ✓ |
|
|
478
|
+
|
|
479
|
+
Porting the missing TypeScript integrations (Langfuse, Claude multi-turn) is
|
|
480
|
+
planned; new TS integrations must keep V1 API response shapes backward-compatible.
|
|
329
481
|
|
|
330
482
|
## License
|
|
331
483
|
|
|
332
484
|
MIT
|
|
485
|
+
|
|
486
|
+
## Delivery diagnostics and worker handoffs
|
|
487
|
+
|
|
488
|
+
### Large tool content (opt-in preview)
|
|
489
|
+
|
|
490
|
+
For enabled workspaces, the native SDK can upload serialized tool inputs/outputs larger than 64 KiB directly to private storage and attach a small reference to the tool span. The configured PII policy runs before byte counting, hashing and upload. Enable the policy fields you need; `redactionRevision` labels that policy and does not itself redact data.
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
const client = new SentrialClient({
|
|
494
|
+
apiKey: process.env.SENTRIAL_API_KEY,
|
|
495
|
+
pii: { enabled: true },
|
|
496
|
+
largeToolContent: {
|
|
497
|
+
enabled: true,
|
|
498
|
+
redactionRevision: 'claims-redaction-v1',
|
|
499
|
+
timeoutMs: 20_000,
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
This remains disabled by default and requires separately enabled server storage and workspace quotas. It applies to named `tool_call` input/output fields, including calls recorded through the native SDK integrations; arbitrary external OpenTelemetry spans, LLM messages, metadata and state snapshots are not uploaded. The [Python clients](../python-sdk/README.md#large-tool-content-opt-in-preview) implement the same opt-in; hosted acceptance remains pending.
|
|
505
|
+
|
|
506
|
+
Each field retains a prefix of at most 4 KiB of UTF-8 source bytes inline, with a 16 MiB object limit and two simultaneous uploads per client. Excess concurrent fields, quota failures, unavailable storage, cancellation and oversized objects keep the excerpt plus an explicit missing-content marker. Small fields and clients without the opt-in keep their existing inline behavior. Uploads use at most 24 requests and a configurable 1–60 second per-field deadline (20 seconds by default); this does not bound the subsequent normal trace delivery. A lost PUT acknowledgement is reconciled against retained parts before any retry. Recovery is in memory; restarting a process does not resume its SDK upload.
|
|
507
|
+
|
|
508
|
+
`completeSession()` and `flush()` wait for uploads already started before draining their telemetry. `shutdown()` cancels outstanding uploads and drains their fallback events; call `flush()` first when you want to wait for uploads. Do not emit more events after finishing a session. Abrupt process exit cannot guarantee delivery.
|
|
509
|
+
|
|
510
|
+
`getContentUploadDiagnostics()` returns payload-free attempted/submitted/missing/active/pending-event counts, or `null` when disabled. **Submitted is not verified or inspected:** the server must verify stored bytes, and a detector must separately load the relevant content within its evidence budget. A UI preview also does not establish detector coverage. See the [implementation and rollout limits](../../docs/plans/2026-10-17-telemetry-large-content.md).
|
|
511
|
+
|
|
512
|
+
### Request and delivery diagnostics
|
|
513
|
+
|
|
514
|
+
For request latency and response reports, read `client.getTransportDiagnostics()` (also available without batching). It separates logical requests, retries, in-flight attempts, response outcomes, reported span acceptance and fixed server timing stages. It retains lifetime aggregates plus 32 recent completed attempts, with no payloads or identifiers. Missing values stay unknown; a successful HTTP response is not proof of unique durable writes. [Usage and measurement limits](../../docs/testing/sdk-transport-timing.md).
|
|
515
|
+
|
|
516
|
+
Call finish after the work belonging to a session has completed, then explicitly flush/close before process exit. Native SDK spans carry source sequence numbers and a final count, so missing events can be detected independently of server arrival order. This proves delivery of emitted spans, not that every application action was instrumented. Queues remain in memory and can drop events at capacity; abrupt termination cannot guarantee a flush.
|
|
517
|
+
|
|
518
|
+
Read `client.getDeliveryDiagnostics()` for payload-free batcher counters (`null` when batching is disabled). A `failed` request was not confirmed delivered; it may have reached the server.
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
import { actionMetadata, resumeHandoffContext } from '@sentrial/sdk';
|
|
522
|
+
|
|
523
|
+
const parent = await client.begin({
|
|
524
|
+
userId: 'example', event: 'workflow',
|
|
525
|
+
metadata: actionMetadata({ actionId: 'order:42' }),
|
|
526
|
+
});
|
|
527
|
+
const carrier = parent.sessionId
|
|
528
|
+
? await client.handoff({ sessionId: parent.sessionId, actionId: 'order:42' })
|
|
529
|
+
: null;
|
|
530
|
+
// Pass carrier through your job queue, then in the worker:
|
|
531
|
+
const child = await workerClient.begin({
|
|
532
|
+
userId: 'example', event: 'worker', ...(carrier ? resumeHandoffContext(carrier) : {}),
|
|
533
|
+
});
|
|
534
|
+
const metadata = actionMetadata({ actionId: 'order:42', attemptId: 'try-2', retryOfAttemptId: 'try-1' });
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
Handoff records delegation before returning the carrier and the worker starts a separate trace linked by parent session. The carrier is correlation metadata, not a credential or verified business outcome. Use opaque identifiers, never customer content or secrets. Low-level `createHandoffContext` / `create_handoff_context` only serializes a carrier; it does not record delegation. V2 signal evidence treats detached outcomes as unresolved until their context is supported.
|
|
538
|
+
|
|
539
|
+
### Account for worker completion
|
|
540
|
+
|
|
541
|
+
The v2 lifecycle adds a dispatch ID and four explicit records. Existing `handoff()` carriers remain supported. Use a fresh UUID per worker execution; preserve it only when retrying delivery of the same dispatch. A retry that executes work again gets a new dispatch ID (and a new `attemptId`, optionally linked with `retryOfAttemptId`). Use opaque identifiers.
|
|
542
|
+
|
|
543
|
+
```typescript
|
|
544
|
+
// Parent: record intent before sending the carrier through your queue/RPC.
|
|
545
|
+
import { randomUUID } from 'node:crypto';
|
|
546
|
+
|
|
547
|
+
const carrier = await parentClient.dispatchHandoff({
|
|
548
|
+
sessionId: parentSessionId,
|
|
549
|
+
actionId: 'order:42',
|
|
550
|
+
dispatchId: randomUUID(),
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
// Worker: use its own session, initialized from the received carrier.
|
|
554
|
+
const worker = await workerClient.begin({
|
|
555
|
+
userId: 'example', event: 'order-worker', ...resumeHandoffContext(carrier),
|
|
556
|
+
});
|
|
557
|
+
const workerSessionId = worker.getSessionId();
|
|
558
|
+
if (workerSessionId) {
|
|
559
|
+
await workerClient.acceptHandoff(workerSessionId, carrier);
|
|
560
|
+
// Execute the work here. Report the actual execution outcome.
|
|
561
|
+
await workerClient.finishHandoff(workerSessionId, carrier, 'completed');
|
|
562
|
+
}
|
|
563
|
+
await worker.finish({ output: 'Worker finished' });
|
|
564
|
+
|
|
565
|
+
// Parent: only after your application receives the worker's terminal result.
|
|
566
|
+
// Send the worker session ID and actual outcome with that result.
|
|
567
|
+
if (workerSessionId) {
|
|
568
|
+
await parentClient.receiveHandoff(parentSessionId, carrier, workerSessionId, 'completed');
|
|
569
|
+
}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
`completed`, `failed` and `cancelled` describe worker execution, not business success. Record `finishHandoff` before closing the worker session and `receiveHandoff` before closing the parent. Do not emit a completion in an unconditional `finally`; crashes, timeouts or lost results must remain unresolved. A telemetry method returning does not prove the queue or collector received anything. Continue using delivery diagnostics and flush/shutdown normally. If telemetry session creation fails, application work should still continue without these optional records.
|
|
573
|
+
|
|
574
|
+
The internal related-evidence reader detects missing phases, duplicate worker claims, conflicting outcomes and invalid local order. It uses SDK producer sequences rather than arrival order or clocks across machines. Captures remain incomplete for definitive signal judgments. See the [handoff verification contract](../../docs/plans/2026-10-09-handoff-lifecycle-delivery.md).
|
|
575
|
+
|
|
576
|
+
### Report experiment exposure explicitly
|
|
577
|
+
|
|
578
|
+
`getVariant()` and `createSession()` associate an assignment with a session, including
|
|
579
|
+
holdouts. An association does **not** assert that your application used the configuration.
|
|
580
|
+
After using the returned settings, report that usage explicitly:
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
const assignment = await client.getVariant({
|
|
584
|
+
experimentName: 'support-prompt',
|
|
585
|
+
agentName: 'Support agent',
|
|
586
|
+
userId: 'stable-user-id',
|
|
587
|
+
});
|
|
588
|
+
const sessionId = await client.createSession({
|
|
589
|
+
name: 'Support request',
|
|
590
|
+
agentName: 'Support agent',
|
|
591
|
+
userId: 'stable-user-id',
|
|
592
|
+
});
|
|
593
|
+
// Apply the returned settings in your application before recording exposure.
|
|
594
|
+
if (sessionId && assignment.assignmentId && assignment.configurationHash) {
|
|
595
|
+
const receipt = await client.recordExperimentExposure({
|
|
596
|
+
sessionId,
|
|
597
|
+
assignmentId: assignment.assignmentId,
|
|
598
|
+
variant: assignment.variant,
|
|
599
|
+
configurationHash: assignment.configurationHash,
|
|
600
|
+
});
|
|
601
|
+
// null means delivery failed under failSilently:true; retry these same fields.
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
Only report when your application used that configuration. Do not gate holdout reporting
|
|
606
|
+
on `assignment.active`: a holdout has `active:false` and `reason:'holdout'`. Historical
|
|
607
|
+
assignments may lack `configurationHash` and cannot produce a verified matching receipt.
|
|
608
|
+
The digest identifies the frozen returned configuration, not a model provider's behavior.
|
|
609
|
+
|
|
610
|
+
Receipts are idempotent per assignment/session; `recordedAt` is server arrival time.
|
|
611
|
+
Conflicting configuration reports return 409. Deleted source sessions return 410, with
|
|
612
|
+
history retained as tombstones. Delayed reports for previously issued assignments may
|
|
613
|
+
arrive after the experiment stops. The method flushes queued telemetry before sending;
|
|
614
|
+
it does not run inference. `failSilently:false` propagates delivery errors.
|
|
615
|
+
|
|
616
|
+
For overlapping sessions on one client, pass `sessionId` to `getVariant()` or pass
|
|
617
|
+
`experimentAssignmentIds` explicitly when creating a session. Automatic linking targets
|
|
618
|
+
the next session and should only be used when that matches your application's ordering.
|
|
619
|
+
Each experiment/session may belong to only one assignment identity.
|
|
620
|
+
|
|
621
|
+
### Assignment identity and telemetry acknowledgment
|
|
622
|
+
|
|
623
|
+
An experiment's declared assignment unit is required: supply `userId`/`user_id` for
|
|
624
|
+
user assignment, `convoId`/`convo_id` for conversation assignment, or
|
|
625
|
+
`anonymousId`/`anonymous_id` for anonymous assignment. Another identifier or a session
|
|
626
|
+
ID will not substitute for it. Missing or whitespace-only identity returns
|
|
627
|
+
`active:false`, `reason:'missing_identity'`, the required `assignmentUnit`, and your
|
|
628
|
+
default variant. No assignment is created. Preserve a stable identifier across requests.
|
|
629
|
+
Pass that same user or conversation identifier when creating the session. A conflicting
|
|
630
|
+
session identifier rejects the link; once linked, it cannot be changed or cleared.
|
|
631
|
+
Assignments from different experiments must also agree on each shared assignment unit.
|
|
632
|
+
An anonymous assignment has no corresponding session identity field, so its consistency
|
|
633
|
+
is checked against the session's other anonymous assignments. This does not verify that
|
|
634
|
+
the caller's identifier represents a real person or remains stable outside this session.
|
|
635
|
+
|
|
636
|
+
New assignments report `identityProvenance:'declared'` and their immutable
|
|
637
|
+
`assignmentUnit`. Existing assignments report `identityProvenance:'legacy_unknown'`
|
|
638
|
+
when their historical fallback identity cannot be established. A lookup does not upgrade
|
|
639
|
+
that provenance. Start a new experiment for a prospectively defined identity/analysis plan.
|
|
640
|
+
|
|
641
|
+
Native telemetry commits a session's assignment links in the same transaction as its
|
|
642
|
+
session/events. A failed link fails the request, and a retry preserves the original trace
|
|
643
|
+
identity. Unknown or foreign assignments cannot be silently skipped. Explicit assignment
|
|
644
|
+
lists must contain at most 64 UUIDs; malformed or oversized lists receive HTTP 400 before
|
|
645
|
+
any trace in that batch writes. UUIDs are deduplicated without dropping distinct entries.
|
|
646
|
+
A valid list whose assignment was deleted or is unavailable fails instead of returning a
|
|
647
|
+
false success; correct the references before retrying a permanent error. The existing SDK
|
|
648
|
+
fail-silent/strict error policy still applies.
|