CrewAI Tutorial (TypeScript): adding observability for intermediate developers

By Cyprian AaronsUpdated 2026-04-21
crewaiadding-observability-for-intermediate-developerstypescript

This tutorial shows you how to add observability to a CrewAI TypeScript project so you can trace agent runs, inspect tool calls, and debug failures without guessing. You need this when your crew starts doing real work and you want visibility into what each agent decided, which step failed, and how long each run took.

What You'll Need

  • Node.js 18+ and npm
  • A TypeScript CrewAI project already set up
  • OpenAI API key
  • An observability backend that accepts OpenTelemetry traces, such as:
    • Langfuse
    • Honeycomb
    • Datadog
    • Jaeger
  • These packages:
    • @crewai/crewai
    • openai
    • dotenv
    • @opentelemetry/api
    • @opentelemetry/sdk-node
    • @opentelemetry/exporter-trace-otlp-http
    • @opentelemetry/auto-instrumentations-node

Step-by-Step

  1. Start with a minimal CrewAI setup that already works. The goal is to get a baseline crew running before adding tracing, because observability is only useful if the underlying execution path is stable.
import "dotenv/config";
import { Agent, Task, Crew } from "@crewai/crewai";

const analyst = new Agent({
  role: "Research Analyst",
  goal: "Summarize customer complaint themes",
  backstory: "You analyze support tickets for recurring issues.",
});

const task = new Task({
  description: "Summarize the top three complaint themes from this ticket text: login fails after password reset.",
  expectedOutput: "A short bullet list of themes.",
  agent: analyst,
});

const crew = new Crew({
  agents: [analyst],
  tasks: [task],
});

const result = await crew.kickoff();
console.log(result);
  1. Add OpenTelemetry initialization before any CrewAI code runs. In production, this needs to happen at process startup so the SDK can patch HTTP and OpenAI calls before the first request is made.
import "dotenv/config";
import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter({
    url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
    headers: process.env.OTEL_EXPORTER_OTLP_HEADERS
      ? Object.fromEntries(
          process.env.OTEL_EXPORTER_OTLP_HEADERS.split(",").map((pair) => {
            const [key, value] = pair.split("=");
            return [key.trim(), value.trim()];
          })
        )
      : undefined,
  }),
  instrumentations: [getNodeAutoInstrumentations()],
});

await sdk.start();
console.log("OpenTelemetry initialized");
  1. Create a traced execution wrapper around your crew run. This gives you a clean span boundary for each business operation, which is what you want in banking and insurance workflows where one request may fan out into multiple agent actions.
import "dotenv/config";
import { trace } from "@opentelemetry/api";
import { Agent, Task, Crew } from "@crewai/crewai";

const tracer = trace.getTracer("crewai-demo");

const analyst = new Agent({
  role: "Research Analyst",
  goal: "Summarize customer complaint themes",
  backstory: "You analyze support tickets for recurring issues.",
});

const task = new Task({
  description: "Summarize the top three complaint themes from this ticket text: login fails after password reset.",
  expectedOutput: "A short bullet list of themes.",
  agent: analyst,
});

const crew = new Crew({ agents: [analyst], tasks: [task] });

const result = await tracer.startActiveSpan("crew.kickoff", async (span) => {
  try {
    const output = await crew.kickoff();
    span.setAttribute("crew.result_type", typeof output);
    return output;
  } finally {
    span.end();
  }
});

console.log(result);
  1. Attach useful metadata to spans so traces are searchable later. The point is not just recording that something ran; it’s making it possible to answer questions like “which customer segment triggered slow runs?” or “which task template produced malformed output?”
import "dotenv/config";
import { trace } from "@opentelemetry/api";
import { Agent, Task, Crew } from "@crewai/crewai";

const tracer = trace.getTracer("crewai-demo");

async function runCrew(requestId: string, tenantId: string) {
  const analyst = new Agent({
    role: "Research Analyst",
    goal: "Summarize customer complaint themes",
    backstory: "You analyze support tickets for recurring issues.",
    verbose: true,
  });

  const task = new Task({
    description:
      "Summarize the top three complaint themes from this ticket text: login fails after password reset.",
    expectedOutput: "A short bullet list of themes.",
    agent: analyst,
  });

  const crew = new Crew({ agents: [analyst], tasks: [task] });

  return tracer.startActiveSpan("crew.request", (span) => {
    span.setAttributes({ request_id: requestId, tenant_id: tenantId });
    return crew.kickoff().finally(() => span.end());
  });
}

console.log(await runCrew("req_123", "tenant_acme"));
  1. Export traces to your backend using environment variables. This keeps the code unchanged across local development, staging, and production, which is the right pattern for regulated environments.
export OPENAI_API_KEY="your-openai-key"
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://api.your-observability-provider.com/v1/traces"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer your-token"

node --loader tsx src/index.ts

Testing It

Run the script once and confirm two things in your observability tool: a parent span named crew.request or crew.kickoff, and child spans created by auto-instrumentation for outbound network calls. If you don’t see anything, check that the exporter endpoint and headers are correct and that your SDK starts before importing or invoking any CrewAI code.

Then introduce a deliberate failure by using an invalid API key or malformed task prompt. You should see the span end with an error status in your tracing backend, along with enough metadata to identify which request caused it.

If you want a quick local check without a vendor backend, point OTLP at a local collector or Jaeger instance and verify traces appear there first. That catches wiring mistakes before you send data to production observability systems.

Next Steps

  • Add custom spans around individual tools so you can see exactly which tool call slowed down or failed.
  • Propagate correlation IDs from your HTTP layer into CrewAI spans for end-to-end request tracing.
  • Add structured logs alongside traces so you can query by request ID when debugging production incidents.

Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides