Files
deepseek-harness/docs/subsystems/session-telemetry.zh.md
T

211 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 遥测(telemetry
[English](session-telemetry.md) | 中文
对外的会话上报拆分为一项[能力 seam](../capability-seams.zh.md)Service Definition 与捕获协调器([dsh-session-telemetry](../../packages/session/session-telemetry)`ctx.sessionTelemetry`)拥有完整的权威事件捕获、`session-telemetry/record` 脱敏 waterfall(瀑布式事件)、handoff 游标与最小后端约定;部署方加载的 Service Provider[dsh-session-telemetry-otel](../../packages/session/session-telemetry-otel))则是原样配置的 OpenTelemetry JS SDK 日志流水线。它是一项可选能力,不属于 agent loop(智能体循环)主干,这里也没有任何内容会进入模型请求。边界公理(harness 的职责止于 `emit()`;批处理、重试、排队与丢失策略都属于上报 SDK)连同被否决的替代方案,均已在[复活 Agent Note](../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.zh.md)中定案;捕获与游标约定见 [Service Definition README](../../packages/session/session-telemetry/README.zh.md)。
源码:[`packages/session/session-telemetry/src/index.ts`](../../packages/session/session-telemetry/src/index.ts)
## 逻辑记录
```ts type-equiv
/**
* Severity of a telemetry record, pre-mapped at capture so a receiver can
* alert with zero configuration: `error` for events whose own outcome flag
* says so (the tool-result block's `isError`, `turn/end` error reasons) and for
* `agent-error` operational records. Captured events otherwise default to
* `info`; `warn` remains available to `session-telemetry/record` policies and
* backends.
*/
type SessionTelemetrySeverity = 'info' | 'warn' | 'error'
```
```ts type-equiv
/**
* One logical record handed to a backend — the capture contract's whole outbound
* vocabulary. Ledger records mirror session-log events one-to-one;
* operational records (`channel: 'ops'`) carry the two signals with no log
* home (`agent-error`, `shutdown`) and deliberately omit `event.seq`-style
* identity so they can never be mistaken for ledger rows.
*/
interface SessionTelemetryRecord {
/** Ledger (session-log mirror) or ops (operational signal) channel; backends keep the two under separate instrumentation scopes. */
channel: 'ledger' | 'ops'
/** Unix epoch milliseconds — the source event's append time for ledger records, the emission time for ops records. */
time: number
/** Pre-mapped alerting severity; see {@link SessionTelemetrySeverity}. */
severity: SessionTelemetrySeverity
/**
* Identity attributes, deliberately minimal: ledger records carry
* `session.id`, `session.format_version`, `event.type`, `event.seq`, plus optional
* `session.cwd` / `session.parent_id`; a seeded Session also carries
* `session.seed_length` from its exact inherited event count;
* ops records carry `telemetry.op`, `session.id`, and (for `agent-error`)
* `agent.id`, `turn`, `step`, `error.name`. Anything recoverable from the
* body is intentionally NOT duplicated here.
*/
attributes: Record<string, string | number>
/**
* The complete payload: a deep copy of the session event's `data` for
* ledger records (JSON-serializable by `Session.append`'s own
* validation), or the op payload for ops records. Never mutated after
* handoff.
*/
body: unknown
}
```
每条权威[会话事件](session.zh.md)都会完整透传为一条有序 ledger 记录,包括每个携带完整紧凑 stream 的 `assistant/message` 或 `assistant/attempt`,以及该 seam 从未听说过、由插件合并进来的类型。进程本地 `agent/assistant-stream` frame 不进入该持久 feed。新 Session 对象从其生命周期边界开始,除非后端选择 `includeHistory`;重新收养同一对象时会从 handoff 游标之后继续。投递是尽力而为的:游标标记的是「已交接」而非「已送达」,记录可能丢失(崩溃、重载窗口)也可能重复(新对象回放、SDK 重试),因此接收端对 ledger 记录基于 `(session.id, session.format_version, event.seq)` 去重;ops 记录刻意省略这类标识——它们是用于告警的信号,而非用于累加的条目,重复被容忍而非被去重。
## 共享披露
每个后端都通过 `ctx.sessionTelemetry` 上必需的抽象 `sharing` 成员暴露其部署级模式([Service Definition README](../../packages/session/session-telemetry/README.zh.md#the-sharing-disclosure))。它既不是逐 Session 的接纳决定,也不是投递回执。`/feedback` 确认文本不查询它。
```ts type-equiv
/**
* Deployment-selected session-sharing mode, not confirmation of SDK delivery.
*/
type SessionTelemetrySharingStatus = 'full' | 'feedback-only' | 'disabled'
```
## 捕获策略
```ts type-equiv
/** Whether capture follows live events or reads the canonical log only when requested. */
type SessionTelemetryCapture = 'live' | 'on-demand'
```
```ts type-equiv
/** Backend-selected capture mode and history policy. */
interface SessionTelemetryCaptureOptions {
/** Follow live events, or wait for explicit capture; defaults to live. */
capture?: SessionTelemetryCapture
/** Include stored history before this lifecycle; defaults to false. */
includeHistory?: boolean
}
```
`includeHistory` 允许捕获存储与继承的记录,但本身不授权捕获。[OTel 后端](../../packages/session/session-telemetry-otel/README.zh.md)使用按需捕获,并要求新的自身显式反馈;它只释放截至该反馈的完整前缀,适用于所有提供方。
## 后端约定
```ts type-equiv
/**
* The minimum backend contract the coordinator requires. {@link SessionTelemetryBackend} is
* its service-registered form; tests compose the coordinator with a bare
* implementation of this interface.
*/
interface SessionTelemetrySink {
/**
* Hand one record to the backend's pipeline. MUST be a non-blocking
* enqueue — the coordinator calls this synchronously from the
* `session/event` hot path or an explicit canonical-log capture, so anything
* slower than a queue push would tax the agent loop or feedback handling.
* Errors thrown here are contained by the coordinator and logged; they
* never reach the loop.
* @param record - the logical record to report; owned by the backend after the call.
*/
emit(record: SessionTelemetryRecord): void
/**
* Optional hint that a turn ended. A backend may forward it to its SDK's
* flush so records are exported after each turn. Called
* fire-and-forget; implementations must not block and must not throw
* meaningfully (the coordinator contains exceptions). Most backends should
* leave this unimplemented and let their SDK's own batching cadence govern
* export timing: a backend that does implement it owns the interaction
* between its concurrent flushes and {@link shutdown}'s drain (the OTel
* backend leaves it unimplemented for exactly that hazard — see the
* revival Agent Note).
*/
flush?(): void
/**
* Forward the fiber's disposal to the SDK: flush whatever is queued and
* reach quiescence, per the SDK's own shutdown contract. Everything
* emitted before this call must still be delivered — including records
* enqueued while a {@link flush} hint is in flight, so a backend whose SDK
* guards against concurrent flushes orders behind the outstanding one (the
* coordinator emits its dispose-time `shutdown` markers immediately before
* calling this). Awaited by the coordinator's dispose; a rejection is
* logged as a warning and never fails application teardown.
* The coordinator captures dispose-time shutdown markers immediately before
* this call for live capture; on-demand capture creates no ops records.
* @returns resolves when the backend's pipeline has quiesced.
*/
shutdown(): Promise<void>
}
```
`SessionTelemetryBackend``ctx.sessionTelemetry`[签名](#ctxsessiontelemetry--sessiontelemetrybackend-abstract-seam))是该约定的可加载形态:每个上下文只允许一个实现,重复加载会抛出异常;后端在其构造函数中组合 seam 的 `SessionTelemetryCoordinator`,以此装配捕获侧。
## 脱敏 waterfall`session-telemetry/record`
每条记录在权威事件副本与 `emit()` 之间都要经过 `session-telemetry/record` [waterfall](../cordis-primer.zh.md#cordis-waterfall-semantics)[事件条目](#session-telemetryrecord--waterfall))。seam 自身不带任何规则:未挂载监听器时,记录以捕获时的原样到达后端;导出数据能干净到什么程度,恰恰取决于部署方挂载了什么规则。监听器通过变换 `next()` 的返回值来堆叠;不调用 `next()` 就返回,即替换其下方的全部逻辑;抛出异常的监听器会在协调器的隔离范围内以 fail-closed 方式扣下这一条记录。脱敏只作用于导出副本;权威会话日志永不改写。
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
<a id="cordis-surface"></a>
## Cordis API
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.zh.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
<a id="ctxsessiontelemetry--sessiontelemetrybackend-abstract-seam"></a>
### `ctx.sessionTelemetry` — `SessionTelemetryBackend` (abstract seam)
Loadable form of the backend contract: one implementation per context — the cordis `Service` registration under the `telemetry` key throws on a duplicate, cordis' standard behavior. A backend composes a SessionTelemetryCoordinator in its constructor to install the capture side.
```ts cordis-catalog
/**
* See {@link SessionTelemetrySink.emit} — that declaration is the contract's one home.
* @param record - the logical record to report; owned by the backend after the call.
*/
abstract emit(record: SessionTelemetryRecord): void
/** See {@link SessionTelemetrySink.flush}. */
flush?(): void
/**
* See {@link SessionTelemetrySink.shutdown}.
* @returns resolves when the backend's pipeline has quiesced.
*/
abstract shutdown(): Promise<void>
```
Source: [`packages/session/session-telemetry/src/index.ts`](../../packages/session/session-telemetry/src/index.ts)
<a id="session-telemetry-events"></a>
### `session-telemetry/*` events
<a id="session-telemetryrecord--waterfall"></a>
#### `session-telemetry/record` — waterfall
Transform one outbound record before it reaches the backend. This waterfall is the Service Definition's redaction extension point. It ships NO rules of its own: the innermost `next()` passes the record through unchanged, and with no listener mounted records reach the backend as captured, so exported data is exactly as clean as the rules a deployment mounts. Listeners stack by transforming `next()`'s return value; returning without `next()` replaces everything beneath. Dispatched synchronously on the capture hot path inside the coordinator's containment: a throwing listener withholds that one record (fail-closed) and never reaches the agent loop. Live capture dispatches at append time; on-demand capture dispatches while reading the canonical log. Redaction applies to the exported copy only; the canonical session log is never rewritten.
```ts cordis-catalog
/**
* Transform one outbound record before it reaches the backend. This
* waterfall is the Service Definition's redaction extension point. It ships NO rules
* of its own: the
* innermost `next()` passes the record through unchanged, and with no
* listener mounted records reach the backend as captured, so exported
* data is exactly as clean as the rules a deployment mounts. Listeners
* stack by transforming `next()`'s return value; returning without
* `next()` replaces everything beneath. Dispatched synchronously on the
* capture hot path inside the coordinator's containment: a throwing
* listener withholds that one record (fail-closed) and never reaches the
* agent loop. Live capture dispatches at append time; on-demand capture
* dispatches while reading the canonical log. Redaction applies to the
* exported copy only; the canonical session log is never rewritten.
* @param record - the candidate record, already the coordinator's own deep
* copy; listeners return a (possibly new) record and must not mutate it.
* @mode waterfall
*/
'session-telemetry/record'(record: SessionTelemetryRecord, next: () => SessionTelemetryRecord): SessionTelemetryRecord
```
Source: [`packages/session/session-telemetry/src/index.ts`](../../packages/session/session-telemetry/src/index.ts)
<!-- END GENERATED cordis-surface -->