遥测(telemetry)
/** * 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'/** * 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`, `event.type`, `event.seq`, plus `session.cwd` / * `session.parent_id` / `session.seed_length` when the header has them; * 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}每个 (turn, step) 只发出第一条 assistant/chunk,即「流已开始」的信号;其余分片在捕获时丢弃,因此传输中的 seq 缺口是常态,绝不是数据丢失的信号。其他所有会话事件类型都会完整透传,包括该 seam 从未听说过、由插件合并进来的事件类型。投递是尽力而为的:游标标记的是「已交接」而非「已送达」,记录可能丢失(崩溃、重载窗口)也可能重复(无游标的重新接管、SDK 重试),因此接收端对 ledger 记录基于 (session.id, event.seq) 去重;ops 记录刻意省略这类标识——它们是用于告警的信号,而非用于累加的条目,重复被容忍而非被去重。
该 seam 的确认契约(归属 Service Definition README 的共享披露段):每个后端都通过 ctx.sessionTelemetry 上必需的抽象 sharing 成员披露其部署级共享策略,消费方只有在未挂载任何遥测服务时才渲染「未配置」。披露只陈述当前策略,绝不承诺投递或留存——交接是非阻塞入队,批处理、重试与丢失策略仍归上报 SDK。
/** * Deployment-selected session-sharing policy disclosed by a mounted * {@link SessionTelemetryBackend} backend to human-facing acknowledgement surfaces (the * `/feedback` command's confirmation text). The seam owns the vocabulary so * any backend can disclose a policy without depending on the OTel package; * the values mirror the OTel backend's serialized `SessionTelemetryMode` choices. */type SessionTelemetrySharingStatus = 'full' | 'feedback-only' | 'disabled'/** * 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,签名)是该约定的可加载形态:每个上下文只允许一个实现,重复加载会抛出异常;后端在其构造函数中组合 seam 的 SessionTelemetryCoordinator,以此装配捕获侧。
脱敏 waterfall:session-telemetry/record
Section titled “脱敏 waterfall:session-telemetry/record”每条记录在投影与 emit() 之间都要经过 session-telemetry/record waterfall(事件条目)。seam 自身不带任何规则:未挂载监听器时,记录以捕获时的原样到达后端;导出数据能干净到什么程度,恰恰取决于部署方挂载了什么规则。监听器通过变换 next() 的返回值来堆叠;不调用 next() 就返回,即替换其下方的全部逻辑;抛出异常的监听器会在协调器的隔离范围内以 fail-closed 方式扣下这一条记录。脱敏只作用于导出副本;权威会话日志永不改写。
Cordis API
Section titled “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) — this section is byte-identical in both language sides of the page. Signature blocks use a ts cordis-catalog fence and keep the original source JSDoc; dispatch modes are defined in the primer, and the framework-inherited ctx API lives in cordis-api/inherited.md.
ctx.sessionTelemetry — SessionTelemetryBackend (abstract seam)
Section titled “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.
/** * 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:148
session-telemetry/* events
Section titled “session-telemetry/* events”session-telemetry/record — waterfall
Section titled “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.
/** * 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