refactor(telemetry): replay feedback sessions without buffering

This commit is contained in:
Turtle
2026-08-06 14:57:30 +08:00
parent 9d9b547d55
commit 4e8067e8d5
24 changed files with 270 additions and 200 deletions
@@ -13,7 +13,7 @@ import type { Agent } from '@deepseek-ai/dsh-agent'
import {
TelemetryCoordinator,
type TelemetryBackend,
type TelemetryDelivery,
type TelemetryCapture,
type TelemetryRecord,
} from '../src/index.ts'
@@ -61,7 +61,7 @@ class FakeBackend implements TelemetryBackend {
async function setup(
backend: FakeBackend = new FakeBackend(),
delivery: TelemetryDelivery = 'immediate',
capture: TelemetryCapture = 'live',
) {
const ctx = new Context()
await ctx.plugin(SessionStore)
@@ -70,7 +70,7 @@ async function setup(
name: 'fake-telemetry',
inject: ['sessions'],
apply: (inner: Context) => {
coordinator = new TelemetryCoordinator(inner, backend, delivery)
coordinator = new TelemetryCoordinator(inner, backend, capture)
},
})
return { ctx, backend, coordinator, fiber }
@@ -178,23 +178,24 @@ describe('TelemetryCoordinator capture', () => {
})
})
describe('TelemetryCoordinator held delivery', () => {
it('releases one pending prefix at a time without handing later records over early', async () => {
const { ctx, backend, coordinator } = await setup(new FakeBackend(), 'held')
const session = liveSession(ctx, 'held-prefix')
describe('TelemetryCoordinator on-demand capture', () => {
it('captures one canonical-log prefix at a time without following later events', async () => {
const { ctx, backend, coordinator } = await setup(new FakeBackend(), 'on-demand')
const session = liveSession(ctx, 'on-demand-prefix')
appendTurn(session)
const firstBoundary = session.events[1]!.seq
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
expect(backend.records).toEqual([])
coordinator.release(session)
coordinator.captureSession(session, firstBoundary)
expect(backend.ledger().map(record => record.attributes['event.type'])).toEqual([
'turn/start',
'user/message',
])
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
expect(backend.ledger()).toHaveLength(2)
coordinator.release(session)
coordinator.release(session)
coordinator.captureSession(session)
coordinator.captureSession(session)
expect(backend.ledger().map(record => record.attributes['event.type'])).toEqual([
'turn/start',
'user/message',
@@ -202,38 +203,42 @@ describe('TelemetryCoordinator held delivery', () => {
])
})
it('stores the capture-time redacted copy rather than re-running policy at release', async () => {
const { ctx, backend, coordinator } = await setup(new FakeBackend(), 'held')
it('runs the currently mounted redaction policy during canonical-log capture', async () => {
const { ctx, backend, coordinator } = await setup(new FakeBackend(), 'on-demand')
const session = liveSession(ctx, 'on-demand-redacted')
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
const disposeRule = ctx.on('telemetry/record', (_record, next) => ({
...next(),
body: { scrubbed: true },
}))
const session = liveSession(ctx, 'held-redacted')
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
coordinator.captureSession(session)
expect(backend.ledger()[0]!.body).toEqual({ scrubbed: true })
disposeRule()
coordinator.release(session)
expect(backend.ledger()[0]!.body).toEqual({ scrubbed: true })
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
coordinator.captureSession(session)
expect(backend.ledger()[1]!.body).toEqual({ turn: 1, reason: { kind: 'completed' } })
})
it('contains each backend failure independently while releasing a batch', async () => {
it('contains each backend failure independently while replaying a prefix', async () => {
const backend = new FakeBackend()
backend.rejectSeq = 1
const { ctx, coordinator } = await setup(backend, 'held')
const { ctx, coordinator } = await setup(backend, 'on-demand')
const warn = vi.spyOn(ctx.logger, 'warn').mockImplementation(() => {})
const session = liveSession(ctx, 'held-failure')
const session = liveSession(ctx, 'on-demand-failure')
appendTurn(session)
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
coordinator.release(session)
coordinator.captureSession(session)
expect(backend.ledger().map(record => record.attributes['event.seq'])).toEqual([0, 2])
expect(warn).toHaveBeenCalled()
})
it('rebuilds an unreleased prefix after coordinator reload', async () => {
it('captures a pending prefix after coordinator reload without retained records', async () => {
const first = new FakeBackend()
const { ctx, fiber } = await setup(first, 'held')
const session = liveSession(ctx, 'held-reload')
const { ctx, fiber } = await setup(first, 'on-demand')
const session = liveSession(ctx, 'on-demand-reload')
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
await fiber.dispose()
expect(first.records).toEqual([])
@@ -241,15 +246,34 @@ describe('TelemetryCoordinator held delivery', () => {
const second = new FakeBackend()
let coordinator!: TelemetryCoordinator
await ctx.plugin({
name: 'fake-telemetry-after-held-reload',
name: 'fake-telemetry-after-on-demand-reload',
inject: ['sessions'],
apply: (inner: Context) => {
coordinator = new TelemetryCoordinator(inner, second, 'held')
coordinator = new TelemetryCoordinator(inner, second, 'on-demand')
},
})
coordinator.release(session)
coordinator.captureSession(session)
expect(second.ledger().map(record => record.attributes['event.seq'])).toEqual([0])
})
it('registers no continuous capture, flush, or ops listeners', async () => {
const { ctx, backend, coordinator, fiber } = await setup(new FakeBackend(), 'on-demand')
const redact = vi.fn((_record: TelemetryRecord, next: () => TelemetryRecord) => next())
ctx.on('telemetry/record', redact)
const session = liveSession(ctx, 'on-demand-ledger-only')
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
await ctx.parallel('session/flush', session)
const agent = { id: 'agent-1', session } as Agent
ctx.emit('agent/error', agent, 1, 1, new Error('local only'))
expect(backend.flush).not.toHaveBeenCalled()
expect(backend.records).toEqual([])
expect(redact).not.toHaveBeenCalled()
coordinator.captureSession(session)
expect(redact).toHaveBeenCalledTimes(1)
await fiber.dispose()
expect(backend.records.map(record => record.channel)).toEqual(['ledger'])
})
})
describe('TelemetryCoordinator adoption', () => {