Documentation Index
Fetch the complete documentation index at: https://docs.tracepilotai.com/llms.txt
Use this file to discover all available pages before exploring further.
CrewAI agents do their work through tools. TracePilot traces those tool invocations using tp.wrapToolCall, which captures the tool name, inputs, outputs, and latency for every call your agents make. Wrap at the tool level, not the agent level — this gives you precise visibility into which tool ran, what it received, and what it returned.
How the pattern works
Each CrewAI tool has an _run method where the actual work happens. You wrap the underlying call inside _run with tp.wrapToolCall. The method still returns its result unchanged; TracePilot captures the span in the background and links it to the parent trace via parentSpanId.
Wrap at the tool level, not the agent level. CrewAI agents orchestrate multiple tools — if you only wrap the agent, you lose per-tool visibility.
Setup
Install dependencies
npm install tracepilot-sdk crewai openai
Initialize TracePilot
Create a shared tp instance your tools can import.// lib/tracer.ts
import { TracePilot } from 'tracepilot-sdk';
export const tp = new TracePilot(process.env.TRACEPILOT_API_KEY!);
Wrap your tool's _run method
Pass the trace’s root spanId as parentSpanId so the tool span appears nested under the agent trace in the dashboard.import { Tool } from 'crewai';
import { tp } from '../lib/tracer';
export class WebSearchTool extends Tool {
name = 'web-search';
description = 'Search the web for up-to-date information on a topic.';
constructor(private parentSpanId: string) {
super();
}
async _run(query: string): Promise<string> {
const { result } = await tp.wrapToolCall(
'web-search',
() => fetchSearchResults(query), // your actual search implementation
this.parentSpanId,
1, // step order within this agent's execution
);
return JSON.stringify(result);
}
}
async function fetchSearchResults(query: string): Promise<{ results: string[] }> {
// Replace with your real search client
return { results: [`Result for: ${query}`] };
}
Mark destructive tools
Tools that write data — sending emails, updating records, posting messages — should set isDestructive: true. This adds a warning badge in the dashboard so you can review these calls before forking and re-running an execution.export class SendEmailTool extends Tool {
name = 'send-email';
description = 'Send an email to a customer.';
constructor(private parentSpanId: string) {
super();
}
async _run(input: string): Promise<string> {
const { to, subject, body } = JSON.parse(input);
const { result } = await tp.wrapToolCall(
'send-email',
() => emailClient.send({ to, subject, body }),
this.parentSpanId,
2,
true, // isDestructive — marks this span with a ⚠ badge in the dashboard
);
return `Email sent to ${to}`;
}
}
This example shows a CrewAI research agent that uses two traced tools — a read-only web search and a destructive database write.
import { Agent, Task, Crew } from 'crewai';
import { tp } from './lib/tracer';
import { WebSearchTool } from './tools/web-search';
import { SaveResultTool } from './tools/save-result';
async function runResearchCrew(topic: string) {
await tp.startTrace('research-crew');
// Start a root span to group all tool calls under one trace
const openai = new (await import('openai')).default({
apiKey: process.env.OPENAI_API_KEY,
});
const messages = [{ role: 'user' as const, content: `Research this topic: ${topic}` }];
const { result: plan, spanId: rootSpanId } = await tp.wrapOpenAI(
() =>
openai.chat.completions.create({
model: 'gpt-4o-mini',
messages,
}),
messages,
undefined,
1,
);
// Pass rootSpanId to tools so their spans appear nested in the dashboard
const searchTool = new WebSearchTool(rootSpanId);
const saveTool = new SaveResultTool(rootSpanId);
const researcher = new Agent({
role: 'Research Analyst',
goal: 'Find accurate, up-to-date information on any topic.',
backstory: 'You are an expert at synthesizing information from multiple sources.',
tools: [searchTool, saveTool],
});
const task = new Task({
description: `Research "${topic}" and save a summary to the database.`,
agent: researcher,
});
const crew = new Crew({
agents: [researcher],
tasks: [task],
});
return crew.kickoff();
}
runResearchCrew('autonomous AI agents in production');
Tracing multiple agents
When your crew has more than one agent, start a trace once and pass the root spanId to each agent’s tools. All tool spans will appear under the same trace in the dashboard, grouped by agent.
async function runMultiAgentCrew(brief: string) {
await tp.startTrace('multi-agent-crew');
const openai = new (await import('openai')).default({
apiKey: process.env.OPENAI_API_KEY,
});
const messages = [{ role: 'user' as const, content: brief }];
// One root span for the entire crew run
const { spanId: crewSpanId } = await tp.wrapOpenAI(
() => openai.chat.completions.create({ model: 'gpt-4o-mini', messages }),
messages,
undefined,
1,
);
// Each agent's tools get the shared crewSpanId as parent
const researcherTools = [new WebSearchTool(crewSpanId)];
const writerTools = [new SaveResultTool(crewSpanId)];
// ... build and run your crew
}
Use isDestructive: true for any tool that modifies external state — database writes, API calls that send messages, file deletions. This prevents accidental side effects when you fork and re-run a failing execution from the dashboard.