Skip to content

Metrics & Telemetry ​

AIDK provides built-in metrics collection and telemetry integration. Track token usage, execution times, and custom metrics across your agents.

Overview ​

Metrics in AIDK flow through the execution hierarchy:

engine:execute
├── metrics: { totalTokens: 250, ... }
│
├── model:generate
│   └── metrics: { inputTokens: 100, outputTokens: 50 }
│
├── tool:execute
│   └── metrics: { apiCalls: 1, latencyMs: 200 }
│
└── model:generate
    └── metrics: { inputTokens: 80, outputTokens: 20 }

When a child procedure completes, its metrics automatically propagate up to the parent. The root execution ends up with aggregated metrics from all children.

Execution Metrics ​

Getting Metrics from ExecutionHandle ​

tsx
const handle = await engine.stream(input, <MyAgent />);

// Stream the response
for await (const event of handle.stream()) {
  // Process events...
}

// Get final metrics
const metrics = handle.getMetrics();
console.log(metrics);
// {
//   inputTokens: 180,
//   outputTokens: 70,
//   totalTokens: 250,
//   ticks: 3,
//   toolCalls: 2,
//   durationMs: 1234
// }

Metrics Shape ​

tsx
interface ExecutionMetrics {
  // Token usage
  inputTokens?: number;
  outputTokens?: number;
  totalTokens?: number;

  // Execution info
  ticks?: number;
  toolCalls?: number;
  durationMs?: number;

  // Custom metrics (you can add your own)
  [key: string]: number | undefined;
}

Custom Metrics ​

Adding Metrics in Components ​

tsx
import { addMetric, setMetric, getMetric } from 'aidk-kernel';

class MyAgent extends Component {
  async onTickEnd(com, state) {
    const ctx = context();

    // Accumulate a metric (adds to existing value)
    addMetric(ctx, 'customApiCalls', 1);

    // Set a metric (overwrites)
    setMetric(ctx, 'lastResponseTime', Date.now());

    // Read a metric
    const calls = getMetric(ctx, 'customApiCalls');
  }
}

Adding Metrics in Tools ​

tsx
const MyTool = createTool({
  name: 'my_tool',
  description: 'A tool that tracks metrics',
  input: z.object({ query: z.string() }),

  handler: async (input) => {
    const ctx = context();
    const start = Date.now();

    const result = await externalApi(input.query);

    // Track custom metrics
    addMetric(ctx, 'externalApiCalls', 1);
    addMetric(ctx, 'externalApiLatencyMs', Date.now() - start);

    return [{ type: 'text', text: JSON.stringify(result) }];
  },
});

Usage Metrics (Token Counting) ​

AIDK provides convenience functions for token usage:

tsx
import { addUsageMetrics, getUsageMetrics } from 'aidk-kernel';

// Add token usage
addUsageMetrics(ctx, {
  inputTokens: 100,
  outputTokens: 50,
});

// Get accumulated usage
const usage = getUsageMetrics(ctx);
// { inputTokens: 100, outputTokens: 50 }

Model adapters call addUsageMetrics automatically after each model call.

Engine Metrics ​

Global Engine Metrics ​

tsx
const engine = createEngine();

// Execute some agents...
await engine.execute(input1, <Agent1 />);
await engine.execute(input2, <Agent2 />);

// Get aggregated engine metrics
const engineMetrics = engine.getMetrics();
console.log(engineMetrics);
// {
//   totalExecutions: 2,
//   completedExecutions: 2,
//   failedExecutions: 0,
//   totalTicks: 7,
//   totalTokens: 500,
//   ...
// }

Telemetry Integration ​

AIDK supports pluggable telemetry providers for distributed tracing.

Setting Up OpenTelemetry ​

tsx
import { Telemetry } from 'aidk-kernel';
import { OTelProvider } from './otel-provider'; // Your adapter

// Configure at app startup
const tracerProvider = // ... your OpenTelemetry setup
Telemetry.setProvider(new OTelProvider(tracerProvider));

TelemetryProvider Interface ​

tsx
interface TelemetryProvider {
  startTrace(name: string): string;
  startSpan(name: string): Span;
  recordError(error: any): void;
  endTrace(): void;
  getCounter(name: string): Counter;
  getHistogram(name: string): Histogram;
}

interface Span {
  end(): void;
  setAttribute(key: string, value: any): void;
  recordError(error: any): void;
}

interface Counter {
  add(value: number, attributes?: Record<string, any>): void;
}

interface Histogram {
  record(value: number, attributes?: Record<string, any>): void;
}

Default NoOp Provider ​

By default, AIDK uses a NoOpProvider that does nothing. This means telemetry has zero overhead unless you configure a real provider.

Example: Custom Telemetry Provider ​

tsx
class DatadogProvider implements TelemetryProvider {
  private tracer: any;

  constructor(tracer: any) {
    this.tracer = tracer;
  }

  startSpan(name: string): Span {
    const ddSpan = this.tracer.startSpan(name);
    return {
      end: () => ddSpan.finish(),
      setAttribute: (key, value) => ddSpan.setTag(key, value),
      recordError: (error) => ddSpan.setTag('error', error),
    };
  }

  // ... implement other methods
}

Telemetry.setProvider(new DatadogProvider(datadogTracer));

Automatic Span Names ​

AIDK creates spans with consistent naming:

OperationSpan Name
Engine executeengine:execute
Engine streamengine:stream
Single tickengine:tick
Model callmodel:generate
Tool executiontool:execute:{toolName}
Forkfork:execute
Spawnspawn:execute

Span Naming Best Practices ​

Span names should be low cardinality (not include unique IDs):

tsx
// Good: Low cardinality
'tool:execute:calculator'
'model:generate'
'engine:tick'

// Bad: High cardinality (includes unique IDs)
'tool:execute:calculator:call-123-abc'
'model:generate:request-456'

Use span attributes for unique identifiers:

tsx
const span = Telemetry.startSpan('tool:execute:calculator');
span.setAttribute('tool_call_id', 'call-123-abc');
span.setAttribute('request_id', ctx.requestId);

Metrics in Fork/Spawn ​

Fork: Metrics Propagate ​

Forked executions propagate their metrics to the parent:

tsx
class ParentAgent extends Component {
  render() {
    return (
      <>
        <Fork
          root={<ChildAgent />}
          waitUntilComplete={true}
          onComplete={(result) => {
            // Child's token usage is now in parent's metrics
          }}
        />
      </>
    );
  }
}

// After execution, parent handle.getMetrics() includes child's metrics

Spawn: Metrics Independent ​

Spawned executions are independent—their metrics don't propagate to the parent:

tsx
<Spawn root={<BackgroundLogger />} />
// BackgroundLogger's metrics are tracked separately

Structured Logging ​

AIDK's logger automatically includes context:

tsx
import { Logger } from 'aidk-kernel';

const log = Logger.get();

log.info('Processing request');
// Output includes: request_id, trace_id, procedure_id, etc.

const toolLog = Logger.for('MyTool');
toolLog.debug('Executing', { expression: '2+2' });
// Output includes: component: 'MyTool' + context fields

Configuring the Logger ​

tsx
Logger.configure({
  level: process.env.LOG_LEVEL ?? 'info',
  contextFields: (ctx) => ({
    request_id: ctx.requestId,
    trace_id: ctx.traceId,
    user_id: ctx.user?.id,
    tenant_id: ctx.user?.tenantId,
  }),
});

Best Practices ​

1. Use Meaningful Metric Names ​

tsx
// Good: Descriptive, namespaced
addMetric(ctx, 'search.apiCalls', 1);
addMetric(ctx, 'search.resultsReturned', results.length);
addMetric(ctx, 'cache.hits', cacheHit ? 1 : 0);

// Less good: Generic
addMetric(ctx, 'count', 1);
addMetric(ctx, 'value', x);

2. Track Business Metrics ​

tsx
// Track what matters for your application
addMetric(ctx, 'orders.processed', 1);
addMetric(ctx, 'recommendations.shown', recommendations.length);
addMetric(ctx, 'userQueries.answered', 1);

3. Use Histograms for Latency ​

tsx
const histogram = Telemetry.getHistogram('tool.latency', 'ms');

const start = Date.now();
const result = await doWork();
histogram.record(Date.now() - start, { tool: 'calculator' });

4. Set Up Alerts on Key Metrics ​

Monitor these metrics in production:

  • totalTokens - Cost tracking
  • failedExecutions - Error rate
  • durationMs - Latency
  • toolCalls - Tool usage patterns

Released under the MIT License.