LangSmith integration
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
- This guide assumes you are already familiar with LangSmith. If you aren't, refer to the LangSmith documentation for more details.
- If you are new to Temporal, we recommend reading Understanding Temporal or taking the Temporal 101 course.
- Ensure you have set up your local development environment by following the Set up your local with the TypeScript SDK guide. When you are done, leave the Temporal Development Server running if you want to test your code locally.
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.
-
Install the
@temporalio/langsmithpackage along withlangsmith, which is a peer dependency.npm install @temporalio/langsmith langsmith -
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 envconst 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.
-
Run the Worker with LangSmith tracing enabled. Ensure the Worker process has access to your LangSmith API key.
export LANGSMITH_TRACING=trueexport LANGSMITH_API_KEY="your-api-key"Tracing is off by default, matching the
langsmithlibrary. With no tracing flag set totrue, the plugin emits nothing.The plugin reads the same flags
langsmithitself uses —LANGSMITH_TRACING(orLANGSMITH_TRACING_V2) and theirLANGCHAIN_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);
}
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
| Option | Default | Meaning |
|---|---|---|
client | new Client() | The LangSmith Client that runs are emitted to. |
addTemporalRuns | false | Emit first-class runs for Temporal operations (StartWorkflow:, RunActivity:, HandleSignal:, …) in addition to your traceable runs. |
projectName | LangSmith default | Target LangSmith project for emitted runs. |
tags | — | Tags attached to every run the plugin emits. |
metadata | — | Metadata 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
| Sample | Demonstrates |
|---|---|
| activity-tracing | A traceable model call inside an Activity, nested under the Workflow and Activity runs. |
| workflow-tracing | Replay-safe traceable calls in a Workflow body — emitted once, never duplicated on replay. |
| agent-pipeline | A multi-step agent whose trace threads through Activities and a Child Workflow. |
| message-handlers | traceable calls inside Signal and Update handlers, nested under each handler's run. |