agent-telemetry
v0.10.0
Published
Lightweight JSONL telemetry for AI agent backends. Zero deps, framework adapters included.
Maintainers
Readme
agent-telemetry
Lightweight JSONL telemetry for easier AI agent consumption. Zero runtime dependencies.
Writes structured telemetry events to rotating JSONL files in development. Falls back to console.log in runtimes without filesystem access (Cloudflare Workers). Includes framework adapters for Hono, Inngest, Express, Fastify, Next.js, Prisma, Supabase, a generic traced fetch wrapper, and browser trace-context helpers.
Spec Conformance
This package implements the Agent Telemetry Specification v1.
| Profile | Status | |---------|--------| | Core | Conformant | | File | Conformant | | Browser | Conformant | | Async | Conformant | | Rotation | Conformant | | Agent Consumption | Conformant | | OTel Projection | Not claimed (planned post-1.0) |
Roles: producer, writer, consumer
All emitted records include record_type: "event", spec_version: 1, and an ISO 8601 timestamp. Field names use snake_case to match the spec wire format. The _trace continuation envelope uses the W3C traceparent string format.
Install
bun add agent-telemetryNode.js users: This package ships TypeScript source (no build step). You'll need a bundler that handles
.tsimports (esbuild, tsup, Vite, etc.).
Demo App
This repo includes a runnable browser-to-backend demo in demo/.
bun run demoThen open http://localhost:3001 and click Run Demo Request.
Telemetry is written to .agent-telemetry/{session}/server-{pid}.jsonl by default.
The demo emits correlated http.request, db.query, and external.call events.
The demo page includes a built-in Recent Telemetry panel and a temporal trace timeline view, and you can follow logs with bun run demo:tail.
The timeline uses configurable synthetic delays so spans are easier to distinguish visually.
The demo binds to 127.0.0.1 by default; set DEMO_HOST=0.0.0.0 if you explicitly want LAN access.
Quick Start
import { createTelemetry, type PresetEvents } from 'agent-telemetry'
// createTelemetry is async (one-time runtime probe). The returned emit() is synchronous.
const telemetry = await createTelemetry<PresetEvents>()
telemetry.emit({
kind: 'http.request',
trace_id: generateTraceId(),
span_id: generateSpanId(),
method: 'GET',
path: '/api/health',
status_code: 200,
outcome: 'success',
duration_ms: 12,
})Each call to emit() appends a JSON line to .agent-telemetry/{session}/server-{pid}.jsonl with auto-injected record_type, spec_version, and timestamp:
{"kind":"http.request","trace_id":"0a1b2c3d4e5f67890a1b2c3d4e5f6789","span_id":"a1b2c3d4e5f67890","method":"GET","path":"/api/health","status_code":200,"outcome":"success","duration_ms":12,"record_type":"event","spec_version":1,"timestamp":"2026-02-24T21:00:00.000Z"}How It Works
The library connects every layer of your stack through a shared trace_id:
Inbound HTTP -> Database Queries -> External API Calls -> Background Jobs
Hono Prisma Traced Fetch Inngest
Express Supabase (PostgREST) Supabase (auth/
Fastify storage/functions)
Next.jsOne trace_id follows a request from the HTTP boundary through database queries, external API calls, and into background job execution. span_id/parent_span_id fields preserve parent-child relationships inside that trace. HTTP adapters use the W3C traceparent header for propagation, enabling interop with OpenTelemetry and other standards-compliant tools. Query your JSONL logs by trace_id to see the full chain.
Full-Stack Example
Create one telemetry instance and share it across adapters:
// lib/telemetry.ts
import { createTelemetry, type PresetEvents } from 'agent-telemetry'
export const telemetry = await createTelemetry<PresetEvents>()// server.ts
import { Hono } from 'hono'
import { Inngest } from 'inngest'
import { createHonoTrace, getTraceContext } from 'agent-telemetry/hono'
import { createInngestTrace } from 'agent-telemetry/inngest'
import { telemetry } from './lib/telemetry'
// --- Background job tracing (define client before use) ---
const inngestTrace = createInngestTrace({
telemetry,
entityKeys: ['userId'],
})
const inngest = new Inngest({ id: 'my-app', middleware: [inngestTrace] })
// --- HTTP tracing ---
const trace = createHonoTrace({
telemetry,
entityPatterns: [
{ segment: 'users', key: 'userId' },
],
})
const app = new Hono()
app.use('*', trace)
// Propagate trace context into background job dispatch
app.post('/api/users/:id/process', async (c) => {
await inngest.send({
name: 'app/user.process',
data: { userId: c.req.param('id'), ...getTraceContext(c) },
})
return c.json({ ok: true })
})The getTraceContext(c) call spreads { _trace: { traceparent: "00-..." } } into the dispatch payload. The Inngest middleware reads _trace.traceparent on the receiving end to continue the trace.
This produces a correlated trace:
{"kind":"http.request","trace_id":"aabb...","span_id":"cc11...","method":"POST","path":"/api/users/550e8400-e29b-41d4-a716-446655440000/process","status_code":200,"outcome":"success","duration_ms":45,"entities":{"userId":"550e8400-..."},"record_type":"event","spec_version":1,"timestamp":"..."}
{"kind":"job.dispatch","trace_id":"aabb...","span_id":"dd11...","parent_span_id":"cc11...","task_name":"app/user.process","outcome":"success","record_type":"event","spec_version":1,"timestamp":"..."}
{"kind":"job.start","trace_id":"aabb...","span_id":"ee22...","parent_span_id":"dd11...","task_name":"my-app/process-user","task_id":"run-abc","record_type":"event","spec_version":1,"timestamp":"..."}
{"kind":"job.end","trace_id":"aabb...","span_id":"ee22...","task_name":"my-app/process-user","task_id":"run-abc","duration_ms":230,"outcome":"success","record_type":"event","spec_version":1,"timestamp":"..."}All four events share the same trace_id. Filter with jq 'select(.trace_id == "aabb...")' to see the full chain.
Custom Events
Extend the type system with your own event kinds:
import { createTelemetry, type HttpEvents, type JobEvents } from 'agent-telemetry'
type MyEvents = HttpEvents | JobEvents | {
kind: 'custom.checkout'
trace_id: string
span_id: string
orderId: string
amount: number
}
const telemetry = await createTelemetry<MyEvents>()
telemetry.emit({
kind: 'custom.checkout',
trace_id: 'abc'.repeat(10) + 'ab',
span_id: 'def'.repeat(5) + 'd',
orderId: 'order-abc',
amount: 4999,
})Custom event kinds must use custom.* prefix (e.g. custom.checkout, custom.cache_hit).
Hono Adapter
import { createHonoTrace, getTraceContext } from 'agent-telemetry/hono'
const trace = createHonoTrace({
telemetry,
entityPatterns: [ // Extract entity IDs from URL path segments
{ segment: 'users', key: 'userId' },
{ segment: 'posts', key: 'postId' },
],
sanitizePath: (path) => // Optional: sanitize paths before emission
path.replace(/[0-9a-f-]{36}/gi, ':id'),
isEnabled: () => true, // Guard function (default: () => true)
})
app.use('*', trace)The middleware:
- Parses the incoming W3C
traceparentheader, or generates a fresh trace ID if absent/invalid - Sets
traceparenton the response for client-side correlation (format:00-{traceId}-{spanId}-01) - Emits
http.requestevents with method, path, status_code, outcome, duration, extracted entities, and span linkage (span_id,parent_span_id) - Extracts entity IDs from URL paths -- looks for a matching
segment, then checks if the next segment is a UUID outcomeis"error"for HTTP 5xx,"success"otherwise
getTraceContext(c) returns { _trace: { traceparent: "00-..." } } for spreading into dispatch payloads. Returns {} if no trace middleware is active.
Inngest Adapter
import { createInngestTrace } from 'agent-telemetry/inngest'
const trace = createInngestTrace({
telemetry,
name: 'my-app/trace', // Middleware name (default: 'agent-telemetry/trace')
entityKeys: ['userId', 'orderId'], // Keys to extract from event.data (default: [])
})
const inngest = new Inngest({ id: 'my-app', middleware: [trace] })The middleware:
- Emits
job.startandjob.endevents for function lifecycle (with duration and error tracking) - Emits
job.dispatchevents for outgoing event sends (withoutcome: "success") - Reads trace context from
_trace.traceparentinevent.data(set bygetTraceContext()at the dispatch site) - Generates a new
trace_idwhen no_traceis present, so every function run is always traceable - Uses spec field names:
task_name(function ID),task_id(run ID)
Fetch Adapter
Wraps any fetch call with telemetry. Does not monkey-patch the global -- returns a new function with identical semantics.
import { createTracedFetch } from 'agent-telemetry/fetch'
const fetch = createTracedFetch({
telemetry,
baseFetch: globalThis.fetch, // Optional -- default: globalThis.fetch
getTraceContext: () => ctx, // Optional -- correlate with parent request
propagateTo: (url) => url.origin === 'https://api.my-app.com', // Optional header allowlist
onResponseTraceparent: (tp) => { // Optional response callback
console.log(tp)
},
isEnabled: () => true, // Optional guard
})
const res = await fetch('https://api.stripe.com/v1/charges', { method: 'POST' })- Emits
external.callevents withservice(hostname),operation(METHOD /pathname), and span linkage (span_id, optionalparent_span_id) duration_msmeasures time-to-headers (TTFB) -- the Response body is returned untouched for streaming- Handles all three fetch input types:
string,URL,Request - Can inject outbound
traceparentheaders usingpropagateTo(default: same-origin only in browser, disabled elsewhere) - HTTP 5xx responses get
outcome: "error"; 1xx-4xx getoutcome: "success" - Network errors re-throw after emitting with
outcome: "error"
Browser Trace Context
The browser module connects client-side user actions to server-side traces through a three-step flow:
- Server renders a meta tag with the current
traceparentduring SSR/page render - Browser bootstraps from that meta tag, inheriting the same trace
- Traced fetch injects
traceparenton outgoing requests, closing the loop
Step 1: Server-Side -- Inject the Meta Tag
Your page handler renders a <meta> tag containing the current trace context. This bridges the server render and client-side JavaScript.
// server.ts (Hono example -- same pattern works with Express/Fastify/Next.js)
import { createHonoTrace, getTraceContext } from 'agent-telemetry/hono'
import { formatTraceparent, parseTraceparent } from 'agent-telemetry'
app.use('*', createHonoTrace({ telemetry }))
app.get('/', (c) => {
// getTraceContext returns { _trace: { traceparent: "00-..." } }
const ctx = getTraceContext(c)
const traceparent = '_trace' in ctx ? ctx._trace.traceparent : ''
return c.html(`<!doctype html>
<html>
<head>
<meta name="agent-telemetry-traceparent" content="${traceparent}" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/client.js"></script>
</body>
</html>`)
})Step 2: Browser Bootstrap
The browser reads the meta tag automatically. No manual parsing needed -- createBrowserTraceContext() checks <meta name="agent-telemetry-traceparent"> by default.
// client.ts
import { createBrowserTraceContext, createBrowserTracedFetch } from 'agent-telemetry/browser'
// Bootstraps from <meta name="agent-telemetry-traceparent"> automatically
const trace = createBrowserTraceContext()
// Wrap fetch to inject traceparent on same-origin requests
const tracedFetch = createBrowserTracedFetch({
trace,
propagateTo: (url) => url.origin === window.location.origin,
})Step 3: Traced API Calls
Every tracedFetch call sends the traceparent header. The server adapter picks it up and continues the same trace.
// Simple fetch -- traceparent injected automatically
const response = await tracedFetch('/api/users/123')
// Group multiple calls under a named span
const result = await trace.withSpan('checkout', async (ctx) => {
// Both calls share the same parent span
const cart = await tracedFetch('/api/cart')
const order = await tracedFetch('/api/orders', {
method: 'POST',
body: JSON.stringify({ items: await cart.json() }),
})
return order.json()
})What the Trace Looks Like
A single user action produces a connected trace across browser and server:
Browser Server
------- ------
[page load]
meta tag: 00-{traceId}-{spanA}-01
GET / -> http.request (spanA)
[user clicks "checkout"]
withSpan("checkout")
tracedFetch /api/cart ------> GET /api/cart -> http.request (spanB, parent=spanA)
db.query (spanC, parent=spanB)
tracedFetch /api/orders ----> POST /api/orders -> http.request (spanD, parent=spanA)
db.query (spanE, parent=spanD)
external.call (spanF, parent=spanD)All spans share one trace_id. Query with jq 'select(.trace_id == "...")' to see the full chain from page render through checkout.
API Reference
createBrowserTraceContext(options?)-- creates the trace context manager- Bootstraps from:
initialTraceparentoption -><meta name="agent-telemetry-traceparent">-> fresh IDs trace.getTraceparent()returns the current W3C traceparent stringtrace.withSpan(name, fn)creates a child span, restores the parent after completion- Custom meta tag name: pass
metaName: "my-custom-name"for backwards compatibility
- Bootstraps from:
createBrowserTracedFetch(options?)-- wrapsfetchwith trace propagationpropagateTocontrols which origins receive thetraceparentheader (default: same-origin only)- Response adoption is disabled by default -- set
updateContextFromResponse: trueto enable - Does not emit telemetry events (the server adapter handles that)
Prisma Adapter
Traces all Prisma model operations via $extends(). No runtime @prisma/client import -- the extension is structurally compatible.
import { createPrismaTrace } from 'agent-telemetry/prisma'
const prisma = new PrismaClient().$extends(createPrismaTrace({
telemetry,
getTraceContext: () => ctx, // Optional -- correlate with parent request
isEnabled: () => true, // Optional guard
}))- Emits
db.queryevents withprovider: "prisma",model(e.g."User"),operation(e.g."findMany"), and span linkage (span_id, optionalparent_span_id) - Requires Prisma 5.0.0+ (stable
$extendsAPI) - No access to raw SQL at the query extension level -- model and operation names only
Express Adapter
Standard Express middleware with the same tracing pattern as Hono. No express or @types/express runtime dependency.
import { createExpressTrace, getTraceContext } from 'agent-telemetry/express'
app.use(createExpressTrace({
telemetry,
entityPatterns: [
{ segment: 'users', key: 'userId' },
],
sanitizePath: (path) => path.replace(/[0-9a-f-]{36}/gi, ':id'),
isEnabled: () => true,
}))
app.post('/api/users/:id', (req, res) => {
// Propagate trace context to downstream services
const ctx = getTraceContext(req)
// ctx = { _trace: { traceparent: "00-..." } }
res.json({ ok: true })
})- Emits
http.requestevents with method, path (query string stripped), status_code, outcome, duration, entities, and span linkage - Parses/sets W3C
traceparentheader for propagation - Uses
req.route.pathfor parameterized patterns (e.g./users/:id), falls back toreq.originalUrl - Handles both
res.on("finish")andres.on("close")to capture aborted requests
Fastify Adapter
Fastify plugin using onRequest/onResponse hooks. No fastify runtime dependency -- uses Symbol.for("skip-override") instead of fastify-plugin.
import { createFastifyTrace, getTraceContext } from 'agent-telemetry/fastify'
app.register(createFastifyTrace({
telemetry,
entityPatterns: [
{ segment: 'users', key: 'userId' },
],
sanitizePath: (path) => path.replace(/[0-9a-f-]{36}/gi, ':id'),
isEnabled: () => true,
}))- Emits
http.requestevents usingreply.elapsedTimefor high-resolution duration, including span linkage - Strips query strings from emitted
pathvalues - Parses/sets W3C
traceparentheader for propagation - Uses
request.routeOptions.urlfor parameterized route patterns - Requires Fastify 4.0.0+ (
reply.elapsedTimenot available in 3.x)
Next.js Adapter
Next.js middleware and route handlers run in separate execution contexts, so tracing is split into two pieces: middleware handles trace propagation, route handler wrappers handle timing and event emission. No next runtime dependency.
Middleware -- injects traceparent into request headers for downstream route handlers:
// middleware.ts
import { createNextMiddleware } from 'agent-telemetry/next'
const traceMiddleware = createNextMiddleware()
export function middleware(request: NextRequest) {
return traceMiddleware(request)
}Route handlers -- reads traceparent, measures duration, emits http.request events:
// app/api/users/route.ts
import { withNextTrace } from 'agent-telemetry/next'
export const GET = withNextTrace(async (request) => {
const users = await db.query('SELECT * FROM users')
return Response.json(users)
}, { telemetry })Server Actions -- wraps actions with method: "ACTION" events:
// app/actions.ts
'use server'
import { withActionTrace } from 'agent-telemetry/next'
export const createPost = withActionTrace(async (formData: FormData) => {
// ...
}, { telemetry, name: 'createPost' })createNextMiddleware()parses incomingtraceparent(or generates fresh IDs), creates a child span, and forwards the newtraceparentviaNextResponse.next({ request: { headers } })withNextTrace(handler, options)reads the propagatedtraceparent, times the handler withperformance.now(), and emitshttp.requestwith method, path, status_code, outcome, duration, entities, and span linkagewithActionTrace(action, options)creates a standalone span and emits events withmethod: "ACTION"andpath: actionNamegetTraceContext(request)parsestraceparentfrom request headers and returns{ _trace: { traceparent: "00-..." } }for passing to fetch/prisma/supabase adapters- Supports
entityPatterns,sanitizePath, andisEnabledoptions on route handler wrappers (same as other HTTP adapters) - Uses only Web APIs (Headers, Response, performance.now) -- works in both Node and Edge runtimes
Supabase Adapter
A traced fetch that parses Supabase URL patterns to emit rich, service-aware telemetry. PostgREST calls become db.query events; auth/storage/functions calls become external.call events.
import { createClient } from '@supabase/supabase-js'
import { createSupabaseTrace } from 'agent-telemetry/supabase'
const tracedFetch = createSupabaseTrace({ telemetry })
const supabase = createClient(url, key, { global: { fetch: tracedFetch } })URL pattern classification:
| Pattern | Event | Fields |
|---------|-------|--------|
| /rest/v{N}/{table} | db.query | model: table, operation: select\|insert\|update\|delete |
| /auth/v{N}/{endpoint} | external.call | service: "supabase-auth" |
| /storage/v{N}/object/{bucket} | external.call | service: "supabase-storage" |
| /functions/v{N}/{name} | external.call | service: "supabase-functions" |
- Each
fetchinvocation emits one event -- Supabase's built-in retry logic generates separate events per retry - Realtime (WebSocket) subscriptions are not intercepted (they don't use
fetch) external.callevents useoutcome: "error"for HTTP 5xx;db.queryevents useoutcome: "error"for any non-2xx (PostgREST errors are query failures)
Consumer
The consumer module parses JSONL telemetry files and produces canonical trace summaries for AI agent consumption.
import {
processTelemetry,
processTelemetryDir,
type TraceSummary,
} from 'agent-telemetry/consumer'
// From a string
const result = processTelemetry(jsonlContent)
// From a directory of .jsonl files
const result = await processTelemetryDir('.agent-telemetry/my-session/')
for (const summary of result.summaries) {
console.log(summary.trace_id, summary.event_count)
console.log(summary.uncertainties) // data quality signals
console.log(summary.entities) // aggregated entity values
}The consumer pipeline runs six stages: parse (JSONL lines) -> validate (record_type, spec_version, known kinds) -> normalize (field truncation) -> reconstruct (span trees from trace_id/span_id/parent_span_id) -> uncertainty (data quality annotations) -> summary (canonical output with trust classification).
Each TraceSummary includes:
eventswith per-fieldtrustclassification (system_asserted,untrusted_input,derived,unknown)uncertaintiesfor data quality signals (malformed lines, missing parent spans, writer fallbacks)entitiesaggregated across all events in the trace- Control characters escaped for prompt safety
Lower-level APIs are also exported: parseLine, parseContent, parseFile, parseDirectory, reconstructTraces, buildEntityIndex, lookupEntity, classifyTrust, escapeControlChars.
Configuration
const telemetry = await createTelemetry({
logDir: '.agent-telemetry/my-session', // Directory for log files (default: auto-discovered)
filename: 'telemetry.jsonl', // Log filename (default: '{role}-{pid}.jsonl')
maxSize: 5_000_000, // Max file size before rotation (default: 5MB)
maxBackups: 3, // Number of rotated backups (default: 3)
maxRecordSize: 1_048_576, // Max record size before dropping (default: 1MB)
prefix: '[TEL]', // Console fallback prefix (default: '[TEL]')
isEnabled: () => true, // Guard function (default: () => true)
sessionId: 'my-session', // Session ID for directory structure
role: 'worker', // Role identifier for filename (default: 'server')
sanitizePath: (p) => p.replace(/[0-9a-f-]{36}/gi, ':id'), // Path sanitizer
})Output path discovery order:
- Explicit
logDir+filenameconfig AGENT_TELEMETRY_FILEenvironment variable (single-file mode)AGENT_TELEMETRY_DIRenvironment variable{project_root}/.agent-telemetry/{session_id}/{role}-{pid}.jsonl(auto-discovered)
Project root is detected by walking up from cwd() looking for .git, package.json, or deno.json.
The .agent-telemetry/ directory is automatically added to .gitignore and created with restricted permissions (0o700 directory, 0o600 files on POSIX).
When isEnabled returns false, emit() is a no-op. Useful for environment-based guards:
const telemetry = await createTelemetry({
isEnabled: () => process.env.NODE_ENV === 'development',
})Field Truncation
Fields are automatically truncated to spec-defined byte limits before emission. Truncation is UTF-8 safe (never splits multi-byte characters) and appends ...[truncated] as a suffix.
Key limits: kind (64 bytes), path (1024 bytes), error_name (120 bytes), most other string fields (256 bytes). trace_id, span_id, and parent_span_id are never truncated.
Entity keys are limited to 64 bytes, entity values to 256 bytes.
Preset Event Types
| Type | Events | Description |
|------|--------|-------------|
| HttpEvents | http.request | HTTP request/response telemetry |
| JobEvents | job.start, job.end, job.dispatch | Background job lifecycle |
| ExternalEvents | external.call | External service calls |
| DbEvents | db.query | Database query telemetry |
| SupabaseEvents | db.query, external.call | Supabase-specific union |
| PresetEvents | All of the above | Combined preset union |
Utilities
import {
generateTraceId,
generateSpanId,
extractEntities,
extractEntitiesFromEvent,
formatTraceparent,
parseTraceparent,
truncateField,
} from 'agent-telemetry'
generateTraceId() // -> '0a1b2c3d4e5f67890a1b2c3d4e5f6789' (32 hex chars)
generateSpanId() // -> '0a1b2c3d4e5f6789' (16 hex chars)
// Format a W3C traceparent header
formatTraceparent(traceId, spanId, '01')
// -> '00-0a1b2c3d4e5f67890a1b2c3d4e5f6789-0a1b2c3d4e5f6789-01'
// Parse a traceparent header
parseTraceparent('00-0a1b...6789-0a1b...6789-01')
// -> { version: '00', traceId: '0a1b...', parentId: '0a1b...', traceFlags: '01' }
// Extract entity IDs from URL paths (matches UUID segments only)
extractEntities('/api/users/550e8400-e29b-41d4-a716-446655440000/posts/6ba7b810-9dad-11d1-80b4-00c04fd430c8', [
{ segment: 'users', key: 'userId' },
{ segment: 'posts', key: 'postId' },
])
// -> { userId: '550e8400-...', postId: '6ba7b810-...' }
extractEntities('/api/users/john', [{ segment: 'users', key: 'userId' }])
// -> undefined (non-UUID values are skipped)
// Extract entity IDs from event data objects
extractEntitiesFromEvent({ userId: 'abc', count: 5 }, ['userId', 'postId'])
// -> { userId: 'abc' }
// UTF-8 safe field truncation
truncateField('very long string...', 32)Runtime Detection
The writer automatically detects the runtime environment:
| Runtime | Behavior |
|---------|----------|
| Bun / Node.js | Writes to filesystem with size-based rotation |
| Cloudflare Workers | Falls back to console.log with [TEL] prefix |
Detection happens once during createTelemetry() -- it probes the filesystem by creating the log directory and verifying it exists. Cloudflare's nodejs_compat stubs succeed silently on mkdirSync but fail the existence check, triggering the console fallback.
The returned emit() function is synchronous, non-blocking, and never throws, even with malformed data or filesystem errors. Telemetry must not crash the host application.
Contract Pack
The contracts/agent-telemetry/v1/ directory contains machine-readable contract artifacts for the spec:
- JSON Schemas for all event kinds, diagnostics, and trace summaries
- Field byte limits, enums, and regex patterns
- A glossary with field semantic descriptions
- Negative test vectors for conformance testing
- A manifest with SHA-256 hashes for integrity verification
Migrating from 0.5.x
If you're upgrading from agent-telemetry 0.5.x, the following breaking changes apply:
1. _trace envelope format
The _trace continuation envelope now uses a W3C traceparent string instead of decomposed fields.
// Before (0.5.x)
getTraceContext(c) // -> { _trace: { trace_id: "...", parent_span_id: "...", trace_flags: "01" } }
// After (0.6.0+)
getTraceContext(c) // -> { _trace: { traceparent: "00-{traceId}-{spanId}-01" } }2. Job event field renames
Job events use spec-standard field names:
| 0.5.x field | 1.0 field |
|-------------|-----------|
| function_id | task_name |
| run_id | task_id |
| event_name | task_name |
job.dispatch events now always include an outcome field. New optional fields: queue, attempt.
3. external.call outcome for 5xx
The fetch adapter now sets outcome: "error" for HTTP 5xx responses. Previously all HTTP responses were outcome: "success".
4. Browser meta tag name
The default meta tag name changed from "traceparent" to "agent-telemetry-traceparent". Update your server-rendered HTML:
<!-- Before -->
<meta name="traceparent" content="00-...">
<!-- After -->
<meta name="agent-telemetry-traceparent" content="00-...">Or pass metaName: "traceparent" to createBrowserTraceContext() for backwards compatibility.
5. Response adoption default
createBrowserTracedFetch() no longer adopts response traceparent headers by default. Set updateContextFromResponse: true explicitly if needed.
6. File directory structure
Default output path changed from logs/telemetry.jsonl to {project_root}/.agent-telemetry/{session_id}/{role}-{pid}.jsonl. Pass logDir and filename to keep the old behavior:
const telemetry = await createTelemetry({
logDir: 'logs',
filename: 'telemetry.jsonl',
})License
MIT
