TypeScript SDK
The Kelet TypeScript SDK instruments your Node.js agent to send traces and signals. Built on OpenTelemetry, it works in any Node.js environment — Next.js, Express, serverless. Use agenticSession() to declare session boundaries and signal() to capture feedback.
npm install kelet | Requires Node.js
Install
Section titled “Install”Install kelet and required OTEL peer dependencies:
npm install kelet @opentelemetry/api @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-httppnpm add kelet @opentelemetry/api @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-httpyarn add kelet @opentelemetry/api @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-httpAPI keys
Section titled “API keys”Two keys — never mix them:
- Secret key (
sk_...): server-only. Use this inconfigure(). Never expose it in frontend code. - Publishable key (
pk_...): frontend-safe, for the React SDK only. Cannot send traces.
Get both from Settings → API Keys in the console.
configure()
Section titled “configure()”Call once at startup, before any agent code runs.
import { configure } from 'kelet';
configure({ apiKey: process.env.KELET_API_KEY, project: 'my-agent',});| Option | Type | Description |
|---|---|---|
apiKey | string | Required. Your Kelet secret API key |
project | string | Required. Set here or via KELET_PROJECT env var. |
apiUrl | string | Optional. Override the API base URL |
tracerProvider | BasicTracerProvider | Optional. Attach Kelet’s processor to an existing provider instead of creating one |
spanProcessor | SpanProcessor | Optional. Use this processor instead of Kelet’s default (for wrapping/filtering) |
strict | boolean | Optional. If true, throw when credentials are missing. Default false (log console.warn and disable telemetry) |
If you already have a TracerProvider registered globally, pass it via tracerProvider so Kelet adds its processor to yours rather than registering a second one.
agenticSession()
Section titled “agenticSession()”Groups all work that belongs to one unit. Callback-based — uses AsyncLocalStorage to propagate context. Node.js only.
import { agenticSession } from 'kelet';
const result = await agenticSession( { sessionId: 'abc123', userId: 'user-1' }, async () => { return await agent.run(...); });The callback IS the session boundary. Context propagates through the entire call tree inside the callback.
| Option | Type | Description |
|---|---|---|
sessionId | string | Required. Unique ID for this unit of work |
userId | string | Optional. Associates the session with a user |
metadata | Record<string, string | number | boolean> | Optional. Stamped as metadata.{key} on every span in the session |
signal()
Section titled “signal()”Sends a signal. Returns Promise<void>.
import { signal, SignalKind, SignalSource } from 'kelet';
await signal({ kind: SignalKind.FEEDBACK, source: SignalSource.HUMAN, triggerName: 'user-vote', score: 1.0,});Call inside agenticSession() — sessionId is resolved from context automatically, or pass it explicitly.
| Option | Type | Description |
|---|---|---|
kind | SignalKind | FEEDBACK, EDIT, EVENT, METRIC, or ARBITRARY |
source | SignalSource | HUMAN, LABEL, or SYNTHETIC |
triggerName | string | Optional. Use source-action format: user-vote, user-edit |
score | number | Optional. 0.0–1.0. For votes: 1.0 = positive, 0.0 = negative |
value | string | Optional. Feedback text, diff, reasoning |
confidence | number | Optional. 0.0–1.0. How confident the source is in the signal |
metadata | Record<string, unknown> | Optional. Arbitrary key/value payload attached to the signal |
timestamp | Date | string | Optional. Event timestamp. Defaults to “now” server-side |
sessionId | string | Optional. Resolved from context if omitted |
traceId | string | Optional. Resolved from the active span if omitted |
raiseOnFailure | boolean | Re-raise transport/HTTP errors after retries. Default false |
Transport failures
Section titled “Transport failures”Transport and HTTP errors are retried with exponential backoff (3 attempts). By default, failures after the final attempt are logged via console.warn and swallowed — signal() is a telemetry call and won’t crash your code path. Pass raiseOnFailure: true to opt into re-raising after retries are exhausted. Validation errors (bad score / confidence ranges, missing identifier) always throw.
shutdown()
Section titled “shutdown()”Flushes any pending spans and releases SDK resources. Registered automatically on beforeExit (natural event-loop drain). Call it manually from your own signal handlers or before a hard process.exit(N).
import { shutdown } from 'kelet';
// Flush on SIGINT/SIGTERM from your own handler:process.on('SIGTERM', async () => { await shutdown(); process.exit(143);});The SDK intentionally does not install SIGINT/SIGTERM handlers — attaching a listener suppresses Node’s default exit-on-signal, and calling process.exit() from a library would override your app’s graceful-shutdown logic. Errors from individual processors are logged and swallowed (best-effort).
Context helpers
Section titled “Context helpers”import { getSessionId, getUserId, getMetadata, getTraceId } from 'kelet';
getSessionId(); // current session IDgetUserId(); // current user IDgetMetadata(); // metadata record passed to agenticSessiongetTraceId(); // current trace ID from active spanReturns undefined if called outside agenticSession() (or without an active span for getTraceId).
Next.js
Section titled “Next.js”Use KeletExporter via @vercel/otel in instrumentation.ts:
import { registerOTel } from '@vercel/otel';import { KeletExporter } from 'kelet';
export function register() { registerOTel({ serviceName: 'my-app', traceExporter: new KeletExporter({ apiKey: process.env.KELET_API_KEY, project: 'my-agent', }), });}Two required steps that are easy to miss:
- Enable instrumentation hook in
next.config.js:
experimental: { instrumentationHook: true;}Without this, instrumentation.ts never runs — no traces.
- Enable telemetry per Vercel AI SDK call:
const result = await generateText({ model, prompt, experimental_telemetry: { isEnabled: true },});Vercel AI SDK telemetry is off by default per call.
Use agenticSession() at the route level for Vercel AI SDK (it doesn’t infer sessions automatically):
export async function POST(req: Request) { const { sessionId } = await req.json(); return agenticSession({ sessionId }, async () => { const result = await generateText({ ... }); return Response.json(result); });}Reasoning capture
Section titled “Reasoning capture”To capture reasoning traces:
node --import kelet/reasoning/register app.jsEnvironment variables
Section titled “Environment variables”| Variable | Description |
|---|---|
KELET_API_KEY | API key (required if not passed to configure()) |
KELET_PROJECT | Required project name. SDK throws at startup if missing. |
KELET_API_URL | API base URL |
enum SignalKind { FEEDBACK = 'feedback', // user rating, thumbs, etc. EDIT = 'edit', // user edited AI output EVENT = 'event', // system/app event METRIC = 'metric', // numeric measurement ARBITRARY = 'arbitrary', // custom signal}
enum SignalSource { HUMAN = 'human', // from a real user LABEL = 'label', // from an offline labeling process SYNTHETIC = 'synthetic', // generated by an evaluator or rule}