Skip to main content

LangSmith integration

View Markdown

Temporal's LangSmith integration lets you trace AI agent Workflows in LangSmith alongside every LLM call, tool execution, and Temporal operation.

Temporal gives your agent code durable execution. LangSmith adds the observability side, so you can inspect LLM inputs and outputs, follow a request from the Client through to the model, and compare runs over time.

The LangSmithPlugin is what connects the two. It propagates trace context across Temporal boundaries (Workflow → Activity → Child Workflow) so that runs started on the Client nest correctly under Workflow and Activity runs on the Worker. It can also emit LangSmith runs for the Temporal operations themselves: Workflow executions, Activity executions, Signals, and Updates.

All code snippets in this guide are taken from the LangSmith samples. Refer to the samples for complete code.

Prerequisites

Configure Workers to use LangSmith

Workers execute the code that defines your Workflows and Activities. To trace Workflow and Activity execution in LangSmith, add the LangSmithPlugin to your Worker.

Follow the steps below to configure your Worker.

  1. Install the @temporalio/langsmith package along with langsmith, which is a peer dependency.

    npm install @temporalio/langsmith langsmith
  2. Construct a LangSmith Client and a single shared LangSmithPlugin, then pass the plugin to your Worker.

    import { NativeConnection, Worker } from '@temporalio/worker';
    import { Client as LangSmithClient } from 'langsmith';
    import { LangSmithPlugin } from '@temporalio/langsmith';
    import * as activities from './activities';

    const connection = await NativeConnection.connect({ address: 'localhost:7233' });

    const langsmith = new LangSmithClient(); // reads LANGSMITH_API_KEY from the env
    const plugin = new LangSmithPlugin({ client: langsmith, addTemporalRuns: true });

    const worker = await Worker.create({
    connection,
    taskQueue: 'langsmith',
    workflowsPath: require.resolve('./workflows'),
    activities,
    plugins: [plugin],
    });

    await worker.run();

    Reuse this one instance on both the Worker and Client — see Compose with other plugins.

  3. Run the Worker with LangSmith tracing enabled. Ensure the Worker process has access to your LangSmith API key.

    export LANGSMITH_TRACING=true
    export LANGSMITH_API_KEY="your-api-key"

    Tracing is off by default, matching the langsmith library. With no tracing flag set to true, the plugin emits nothing.

    The plugin reads the same flags langsmith itself uses — LANGSMITH_TRACING (or LANGSMITH_TRACING_V2) and their LANGCHAIN_ aliases.

Configure Clients to use LangSmith

In TypeScript the Client and the Worker are configured independently, so add a LangSmithPlugin to your Client too. This links client-side operations, like starting a Workflow, to the Workflows they trigger.

import { Connection, Client } from '@temporalio/client';

const connection = await Connection.connect();
const client = new Client({ connection, plugins: [plugin] });

Wrap the call that starts your Workflow in a traceable so the rest of the trace nests under your own run.

import { traceable } from 'langsmith/traceable';

const pipeline = traceable(
async () => {
return client.workflow.execute(GreetingWorkflow, {
taskQueue: 'langsmith',
workflowId: 'greeting-1',
args: ['hello'],
});
},
{ name: 'user_pipeline' }
);

await pipeline();

Trace Activities

Any non-deterministic work in a Temporal Workflow (LLM calls, tool executions, database queries, external API calls, and so on) must run inside an Activity. That makes Activities an important place to add LangSmith runs. A traceable from langsmith/traceable works unchanged inside an Activity body: the run shows up in LangSmith nested under RunActivity: for the Activity that scheduled it.

langsmith/activity-tracing/src/activities.ts

import { traceable } from 'langsmith/traceable';

const callModel = traceable(
async (prompt: string): Promise<string> => {
return `answer to: ${prompt}`;
},
{ name: 'inner_llm_call' },
);

export async function answer(prompt: string): Promise<string> {
return callModel(prompt);
}

Trace Workflows

A traceable also works inside a Workflow body, and the plugin keeps it replay-safe: each run is emitted exactly once and is never duplicated when the Workflow replays its history.

langsmith/workflow-tracing/src/workflows.ts

import { traceable } from 'langsmith/traceable';

const extractKeyPoints = traceable(async (text: string): Promise<string> => `points:${text}`, {
name: 'extract_key_points',
});

const summarize = traceable(async (points: string): Promise<string> => `summary:${points}`, {
name: 'summarize',
});

export async function SummarizeWorkflow(text: string): Promise<string> {
const points = await extractKeyPoints(text);
return summarize(points);
}
info

Inside a Workflow body, sequential await inner(...) nesting is exact. Under Promise.all(...) fan-out, or for traceable calls made after an await in the same scope, parenting falls back to the Workflow run. This affects only the visual shape of the trace, never Workflow history or control flow. Activity-side and client-side traceable are unaffected.

Trace Signal and Update handlers

A traceable inside a Signal or Update handler nests under that handler's run, following the same Workflow-body semantics. Temporal-internal Queries (__temporal*, __stack_trace) are never traced.

langsmith/message-handlers/src/workflows.ts

import { traceable } from 'langsmith/traceable';
import { allHandlersFinished, condition, defineSignal, defineUpdate, setHandler } from '@temporalio/workflow';

const classifyMessage = traceable(async (text: string): Promise<string> => `intent:${text}`, {
name: 'classify_intent',
});

const draftReply = traceable(async (text: string): Promise<string> => `reply:${text}`, {
name: 'draft_reply',
});

export const handleMessage = defineSignal<[string]>('handle_message');
export const composeReply = defineUpdate<string, [string]>('compose_reply');
export const complete = defineSignal('complete');

export async function ConversationWorkflow(): Promise<string[]> {
const log: string[] = [];
let done = false;

setHandler(handleMessage, async (text: string) => {
log.push(await classifyMessage(text));
});

setHandler(composeReply, async (text: string) => {
const reply = await draftReply(text);
log.push(reply);
return reply;
});

setHandler(complete, () => {
done = true;
});

await condition(() => done && allHandlersFinished());
return log;
}

Trace multi-step agents across Activities and Child Workflows

A single trace threads through a multi-step agent that calls Activities and a Child Workflow. The parent Workflow gathers facts and writes a report in Activities, then delegates review to a Child Workflow, and every run nests under the same trace.

langsmith/agent-pipeline/src/workflows.ts

import { executeChild, proxyActivities, workflowInfo } from '@temporalio/workflow';
import type * as activities from './activities';

const { gatherFacts, writeReport, reviewReport } = proxyActivities<typeof activities>({
startToCloseTimeout: '1 minute',
});

export async function ReviewWorkflow(report: string): Promise<string> {
return reviewReport(report);
}

export async function ResearchWorkflow(topic: string): Promise<string> {
const facts = await gatherFacts(topic);
const report = await writeReport(facts);
return executeChild(ReviewWorkflow, {
args: [report],
workflowId: `${workflowInfo().workflowId}-review`,
});
}

Include Temporal operations as runs

By default (addTemporalRuns: false), the plugin only propagates LangSmith context so that your traceable runs nest correctly. It does not create its own runs.

Set addTemporalRuns: true if you want first-class runs for the Temporal operations themselves: Workflow executions, Activity executions, Signals, Updates, and so on (StartWorkflow:, RunWorkflow:, StartActivity:, RunActivity:, HandleSignal:, HandleUpdate:). Start* and Run* pairs appear as siblings: the Start* run is emitted by the side scheduling the operation (for example, the Client), and the Run* run is emitted by the side executing it (for example, the Worker).

With the plugin configured on both the Client and the Worker, and addTemporalRuns: true, a trace for a simple LLM call looks like this:

user_pipeline
StartWorkflow:GreetingWorkflow
RunWorkflow:GreetingWorkflow
StartActivity:answer
RunActivity:answer
inner_llm_call

Without addTemporalRuns (the default), only your traceable runs appear. Context still propagates, so they nest correctly under the client-side run:

user_pipeline
inner_llm_call

Plugin options

OptionDefaultMeaning
clientnew Client()The LangSmith Client that runs are emitted to.
addTemporalRunsfalseEmit first-class runs for Temporal operations (StartWorkflow:, RunActivity:, HandleSignal:, …) in addition to your traceable runs.
projectNameLangSmith defaultTarget LangSmith project for emitted runs.
tagsTags attached to every run the plugin emits.
metadataMetadata merged into every run the plugin emits. Credential-looking keys are scrubbed before emission.

The LangSmith API key is never accepted as a plugin option and never crosses a Temporal boundary. Supply a pre-constructed Client (which reads LANGSMITH_API_KEY from the process environment) or let the plugin build a default client from the environment.

Compose with other plugins

Register observability first (outermost) so it observes everything beneath it, then governance, then agent-framework plugins.

const worker = await Worker.create({
connection,
taskQueue: 'langsmith',
workflowsPath: require.resolve('./workflows'),
activities,
plugins: [
new LangSmithPlugin({ client: langsmith }), // observability — first
// new GovernancePlugin(...),
// new AgentFrameworkPlugin(...),
],
});

The plugin de-duplicates its own instrumentation, so a Worker built from a plugin-configured Client will not double-instrument.

Samples

SampleDemonstrates
activity-tracingA traceable model call inside an Activity, nested under the Workflow and Activity runs.
workflow-tracingReplay-safe traceable calls in a Workflow body — emitted once, never duplicated on replay.
agent-pipelineA multi-step agent whose trace threads through Activities and a Child Workflow.
message-handlerstraceable calls inside Signal and Update handlers, nested under each handler's run.