Files
deepseek-harness/packages/telemetry/session-telemetry
_Kerman 02efe3ccea Merge remote-tracking branch 'origin/master' into xtr/react-loop-simplification
# Conflicts:
#	.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml
#	.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md
#	.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.i18n.yaml
#	.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.i18n.yaml
#	.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.zh.md
#	.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.i18n.yaml
#	.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml
#	.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.i18n.yaml
#	.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.zh.md
#	.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.i18n.yaml
#	.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.zh.md
#	.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md
#	.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.zh.md
#	.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md
#	.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-15-replay-token-meter-service.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md
#	.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md
#	.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.zh.md
#	.agents/notes/implemented/architecture/2026-07-28-identified-immutable-message-values.i18n.yaml
#	.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.i18n.yaml
#	.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.zh.md
#	.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.i18n.yaml
#	.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.zh.md
#	.agents/notes/implemented/feature/2026-06-24-workspace-context.i18n.yaml
#	.agents/notes/implemented/feature/2026-06-24-workspace-context.zh.md
#	.agents/notes/implemented/feature/2026-06-30-hook-bridges.i18n.yaml
#	.agents/notes/implemented/feature/2026-06-30-hook-protocol-lib.i18n.yaml
#	.agents/notes/implemented/feature/2026-06-30-hook-protocol-lib.zh.md
#	.agents/notes/implemented/feature/2026-06-30-interception-seams.i18n.yaml
#	.agents/notes/implemented/feature/2026-06-30-interception-seams.zh.md
#	.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md
#	.agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.zh.md
#	.agents/notes/implemented/feature/2026-07-16-harness-level-loop.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-16-harness-level-loop.zh.md
#	.agents/notes/implemented/feature/2026-07-19-human-goal-command.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.zh.md
#	.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.zh.md
#	.agents/notes/implemented/feature/2026-07-21-cross-session-references.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-21-cross-session-references.zh.md
#	.agents/notes/implemented/feature/2026-07-27-tmux-location-context.i18n.yaml
#	.agents/notes/implemented/simplification/2026-06-20-public-agent-stop-surface.i18n.yaml
#	.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.i18n.yaml
#	.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.zh.md
#	.agents/notes/implemented/simplification/2026-07-20-unwrap-injected-content-envelopes.i18n.yaml
#	.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml
#	.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md
#	.agents/notes/implemented/simplification/2026-07-24-agent-loop-observable-state-machine.i18n.yaml
#	.agents/notes/implemented/simplification/2026-07-27-request-error-retry-action.i18n.yaml
#	docs/core-data-structures/compaction.i18n.yaml
#	docs/core-data-structures/goal.i18n.yaml
#	docs/core-data-structures/goal.zh.md
#	docs/core-data-structures/llm-streaming.i18n.yaml
#	docs/core-data-structures/session.i18n.yaml
#	docs/core-data-structures/session.zh.md
#	docs/core-data-structures/skills.i18n.yaml
#	docs/core-data-structures/skills.zh.md
#	docs/core-data-structures/system-prompt.i18n.yaml
#	docs/defensive-patterns.i18n.yaml
#	docs/defensive-patterns.zh.md
#	docs/user/develop/framework/events.i18n.yaml
#	docs/user/develop/framework/events.zh.md
#	packages/acp/acp/README.i18n.yaml
#	packages/client/ui-goal/README.i18n.yaml
#	packages/compact/compact-basic/README.i18n.yaml
#	packages/compact/compact/README.i18n.yaml
#	packages/context/time-context/README.i18n.yaml
#	packages/context/tmux-context/README.i18n.yaml
#	packages/core/agent-loop/README.i18n.yaml
#	packages/core/agent-loop/README.zh.md
#	packages/core/agent/README.i18n.yaml
#	packages/core/agent/README.zh.md
#	packages/core/session/README.i18n.yaml
#	packages/core/session/README.zh.md
#	packages/core/system-prompt/README.i18n.yaml
#	packages/core/system-prompt/README.zh.md
#	packages/examples/cli-demo/README.i18n.yaml
#	packages/goal/command-goal/README.i18n.yaml
#	packages/goal/goal-session/README.i18n.yaml
#	packages/goal/goal/README.i18n.yaml
#	packages/goal/tool-goal/README.i18n.yaml
#	packages/goal/tool-goal/README.zh.md
#	packages/guard/README.i18n.yaml
#	packages/guard/README.zh.md
#	packages/guard/repeat-tool-guard/README.i18n.yaml
#	packages/hooks/hooks-claude/README.i18n.yaml
#	packages/hooks/hooks-codex/README.i18n.yaml
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/README.zh.md
#	packages/llm/llm/README.i18n.yaml
#	packages/plan/plan-mode/README.i18n.yaml
#	packages/plan/plan-mode/README.zh.md
#	packages/sdk/sdk-client/README.i18n.yaml
#	packages/sdk/sdk-client/README.zh.md
#	packages/sdk/sdk-protocol/README.i18n.yaml
#	packages/session-persistence/session-persistence/README.i18n.yaml
#	packages/session-persistence/session-persistence/README.zh.md
#	packages/skill/tool-skill/README.i18n.yaml
#	packages/subagent/subagent-dsh-sdk/README.i18n.yaml
#	packages/subagent/subagent-inprocess/README.i18n.yaml
#	packages/subagent/subagent-inprocess/README.zh.md
#	packages/ui/jsonrpc/README.i18n.yaml
2026-08-05 13:09:53 +08:00
..

@deepseek-ai/dsh-session-telemetry

English | 中文

The telemetry seam: the CAPTURE side of session-event reporting, behind a backend contract any reporting SDK satisfies with zero bending. The boundary axiom that shapes everything here: this package's aspect ends at emit() — batching, retry, queueing, and loss policy belong to the backend's SDK and are neither specified nor wrapped. Rationale and rejected alternatives: the revival Agent Note.

The backend contract

TelemetryBackend is three members: emit(record) (MUST be a non-blocking enqueue — it runs synchronously on the session/event hot path), optional flush() (a turn-boundary hint, fire-and-forget; most backends leave it unimplemented and let their SDK's batching cadence govern export timing — an implementer owns the interaction between concurrent flushes and shutdown()'s drain), and shutdown() (the lifecycle forward: drain-and-quiesce, awaited at dispose). Telemetry is its service-registered form under the telemetry context key — one implementation per context, duplicate load throws. A backend composes TelemetryCoordinator in its constructor.

Capture points

The coordinator registers, all through the composing fiber's effects: session/created (adopt: record the header, read the log back through the projection from the construction boundary — constructor seeds from fork/resume never re-emit on the firehose and never re-export), session/event (project, deep-copy, redact, hand off; zero I/O), session/flush (forward the optional flush() hint and return void — the loop's awaited parallel must never wait on telemetry), session/disposed (emit the session's shutdown operational record at its own termination edge — where receivers key crash detection — then retire it, so a long-lived backend neither retains closed sessions nor re-marks them at unload), agent/error (the one live-bus relay; the session event vocabulary intentionally has no operational-error record), a dispose effect (mark each session still alive at teardown, then await the backend's shutdown(); failures warn instead of throwing), and an adoption sweep of ctx.sessions.list() (a hot reload does not replay session/created).

The redact waterfall

Every record passes the telemetry/record waterfall between projection and emit() — the seam's scrubbing extension point. The seam ships NO rules of its own: the innermost next() passes the record through unchanged, so with no listener mounted records reach the backend exactly as captured, and exported data is precisely as clean as the rules a deployment mounts. Listeners stack by transforming next()'s return value; returning without next() replaces everything beneath, and a throwing listener withholds that one record fail-closed inside the coordinator's containment. Redaction applies to the exported copy only; the canonical session log is never rewritten.

The handoff cursor

A module-scope WeakMap<Session, seq> marks the highest seq HANDED OFF (not delivered) per session, advanced at emit time. It survives reloads that do not re-evaluate this module — config re-applies and backend source reloads, which is where iteration happens; that asymmetry is why the cursor lives in the seam. On re-adoption the coordinator re-hands only events past the cursor (events at or below it still rebuild the chunk-projection state); a missing cursor safely degrades to a re-hand from the session's construction boundary (Session.firstLiveSeq — seq 0 for a session born in this process), absorbed by receiver-side dedupe on (session.id, event.seq). Constructor seeds never re-export: a resumed session's history shipped from the previous process under the same id, and a fork's inherited prefix lives in the parent's stream (receivers stitch on session.parent_id + session.seed_length). The accepted cost, consistent with at-most-once delivery: a resume does not backfill records a previous process failed to deliver — a deployment with a backfill requirement needs the deferred outbox, not replay. This is a deliberate, narrow exception to the registrations-are-effects discipline: entries die with their sessions, the value is a monotonic watermark, and losing it is never an error.

The fixed chunk projection

Only the first assistant/chunk of each (turn, step) ships; the rest are dropped at capture and never advance the cursor. That one chunk is the stream-started signal: step/start + first-chunk presence + assistant/message presence + the turn/end reason distinguish "the request never started" from "the stream died midway" without chunk volume, and time-to-first-token stays computable. Chunk elision makes seq gaps routine on the wire — a gap is never a loss signal. Every other event type, including ones merged by plugins this package never heard of, passes through whole.

The logical record

TelemetryRecord: channel (ledger | ops), time (epoch ms), severity (pre-mapped: ERROR for tool/result.isError, turn/end error reasons, and agent-error; INFO for other captured records, while telemetry/record policies may assign WARN), identity-only attributes (session.id, event.type, event.seq, plus session.cwd/session.parent_id/session.seed_length when the header has them), and the complete deep-copied event.data as body — post-redaction. Operational records carry telemetry.op (agent-error | shutdown) and session.id, and deliberately NO event.seq/event.type — signals to alert on, not entries to sum; agent-error normalizes its arbitrary thrown value into a stable { name, message } body. Delivery downstream of the handoff is the backend SDK's; duplicates remain possible (cursor-less re-adoption, SDK retries), so receivers dedupe on (session.id, event.seq).

Model Experience

None, as the seam only observes the session stream and hands redacted copies to a reporting backend; it never contributes to a model request.

KV Cache effect

None; this package neither assembles nor sends a provider request.

Known Limitations and Deferred Work

  • Best-effort delivery — the cursor marks handed-off, not delivered; a session torn down inside a reload window cannot be re-adopted; whatever sits in a backend queue at crash time is lost. A durable outbox (spool, per-sink cursors, at-least-once) is deferred until a deployment states a crash-loss requirement — see the revival Agent Note.
  • No built-in redaction rules — with no telemetry/record listener mounted, records leave the process exactly as captured, including any credentials embedded in file contents or command output; a deployment exporting to a shared collector owns its rule set.