Skip to content
XLinkedIn
Sign Up →
Ask AI

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 kelet and required OTEL peer dependencies:

Terminal window
npm install kelet @opentelemetry/api @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-http

Two keys — never mix them:

  • Secret key (sk_...): server-only. Use this in configure(). 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.

Call once at startup, before any agent code runs.

import { configure } from 'kelet';
configure({
apiKey: process.env.KELET_API_KEY,
project: 'my-agent',
});
OptionTypeDescription
apiKeystringRequired. Your Kelet secret API key
projectstringRequired. Set here or via KELET_PROJECT env var.
apiUrlstringOptional. Override the API base URL
tracerProviderBasicTracerProviderOptional. Attach Kelet’s processor to an existing provider instead of creating one
spanProcessorSpanProcessorOptional. Use this processor instead of Kelet’s default (for wrapping/filtering)
strictbooleanOptional. 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.

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.

OptionTypeDescription
sessionIdstringRequired. Unique ID for this unit of work
userIdstringOptional. Associates the session with a user
metadataRecord<string, string | number | boolean>Optional. Stamped as metadata.{key} on every span in the session

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.

OptionTypeDescription
kindSignalKindFEEDBACK, EDIT, EVENT, METRIC, or ARBITRARY
sourceSignalSourceHUMAN, LABEL, or SYNTHETIC
triggerNamestringOptional. Use source-action format: user-vote, user-edit
scorenumberOptional. 0.0–1.0. For votes: 1.0 = positive, 0.0 = negative
valuestringOptional. Feedback text, diff, reasoning
confidencenumberOptional. 0.0–1.0. How confident the source is in the signal
metadataRecord<string, unknown>Optional. Arbitrary key/value payload attached to the signal
timestampDate | stringOptional. Event timestamp. Defaults to “now” server-side
sessionIdstringOptional. Resolved from context if omitted
traceIdstringOptional. Resolved from the active span if omitted
raiseOnFailurebooleanRe-raise transport/HTTP errors after retries. Default false

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.

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).

import { getSessionId, getUserId, getMetadata, getTraceId } from 'kelet';
getSessionId(); // current session ID
getUserId(); // current user ID
getMetadata(); // metadata record passed to agenticSession
getTraceId(); // current trace ID from active span

Returns undefined if called outside agenticSession() (or without an active span for getTraceId).

Use KeletExporter via @vercel/otel in instrumentation.ts:

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:

  1. Enable instrumentation hook in next.config.js:
experimental: {
instrumentationHook: true;
}

Without this, instrumentation.ts never runs — no traces.

  1. 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);
});
}

To capture reasoning traces:

Terminal window
node --import kelet/reasoning/register app.js
VariableDescription
KELET_API_KEYAPI key (required if not passed to configure())
KELET_PROJECTRequired project name. SDK throws at startup if missing.
KELET_API_URLAPI 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
}