12. Workflow vs LLM Telemetry
Use this page when deciding whether an integration should create one normal LLM Activity row or a grouped agent workflow timeline.
AgentID cannot infer your business workflow perfectly from a provider call alone. The integration must tell AgentID when several operations belong to one agent run by passing workflow telemetry.
The Short Rule
- Simple chatbot or one-off LLM call: do not set
workflowRunId. - Agent, tool chain, background job, or multi-step automation: set one shared
workflowRunIdfor the whole run. - Every real provider call still needs its own
clientEventIdcorrelation.
This is intentional. workflowRunId answers "which business run is this part
of?" while clientEventId answers "which single guarded provider call or
operation is this?"
What The Dashboard Shows
Simple LLM / chatbot request
Use this for a normal chat route, a single generateText() call, or one
chat.completions.create() call.
Required shape:
{
clientEventId: "one-id-for-this-provider-call",
// no workflowRunId
}
Expected dashboard behavior:
- one Activity row
- the row represents the LLM completion
- the detail view includes the related prompt preflight / guard step
- no separate workflow row
This keeps normal chatbot audit UX compact. Operators should not see two top-level rows for one ordinary user message.
Agent workflow / multi-step run
Use this when a user prompt starts a larger run that may include planning, tool calls, retrieval, delivery, callbacks, or multiple LLM steps.
Required shape:
const workflowRunId = "support-triage-2026-06-20-0001";
// Initial guarded provider call.
{
clientEventId: "llm-call-1",
workflowRunId,
workflowName: "Support triage",
workflowStepName: "initial_prompt",
workflowStepIndex: 0,
}
// Tool or operation event.
{
clientEventId: "tool-call-1",
workflowRunId,
workflowName: "Support triage",
workflowStepName: "fetch_customer_record",
workflowStepIndex: 1,
}
Expected dashboard behavior:
- one workflow summary row with
Open Workflow - standalone prompt / guard audit rows for guarded prompts inside that workflow
- workflow detail timeline groups the surrounding tool, delivery, inbox, and
LLM steps by
workflowRunId
This is not considered duplicate logging. It gives security teams a direct prompt-inspection row while operations teams still get the full workflow timeline.
Which Fields Decide The Mode
| Field | Use | Notes |
|---|---|---|
workflowRunId / workflow_run_id | Agent workflow grouping | Set only when several events belong to one business run. |
workflowName / workflow_name | Human label for the run | Example: Support triage. |
workflowStepId / workflow_step_id | Stable id for one step | Optional but useful for retries and debugging. |
workflowStepName / workflow_step_name | Human label for one step | Example: initial_prompt, search_docs, send_email. |
workflowStepIndex / workflow_step_index | Step ordering | Use zero-based order when possible. |
clientEventId / client_event_id | One protected unit of work | Do not reuse one id for the whole workflow. |
event_subtype: "prompt_preflight_evaluated" | Guard/preflight event | Created by official wrappers before provider execution. |
runtime_surface | Integration surface | Examples: vercel_ai_sdk_guard, vercel_ai_sdk_wrapper, openai_sdk_guard. |
Decision Table
| Scenario | Set workflowRunId? | Dashboard intent |
|---|---|---|
| User sends one chat message to one LLM call | No | One compact LLM row. |
API route calls streamText() once for a chatbot response | No | One compact LLM row. |
| User prompt starts an agent with tools and final answer | Yes | Workflow row plus prompt audit row. |
| Background job summarizes 100 tickets independently | Usually no per ticket, unless each ticket has a multi-step run | Keep unrelated jobs separate. |
| LangChain/LlamaIndex-style chain with retriever, tools, and model calls | Yes | Group all steps into one workflow. |
| Manual masked logging after a raw provider call | Invalid | Protect before provider execution instead. |
Vercel AI SDK Example
Simple chatbot:
const result = await streamText({
model: withAgentId(openai("gpt-4o-mini"), {
systemId: process.env.AGENTID_SYSTEM_ID!,
apiKey: process.env.AGENTID_API_KEY!,
}),
messages,
});
Agent workflow:
import { createAgentIdCorrelationId } from "agentid-vercel-sdk";
const workflowRunId = createAgentIdCorrelationId();
const result = await streamText({
model: secureModel,
messages,
providerOptions: {
agentid: {
telemetry: {
workflowRunId,
workflowName: "Research assistant",
workflowStepName: "initial_prompt",
workflowStepIndex: 0,
},
},
},
});
Then log tool and delivery steps with the same workflowRunId and their own
clientEventId.
Node.js SDK Example
const workflowRunId = createAgentIdCorrelationId();
const secured = agent.wrapOpenAI(openai, {
system_id: process.env.AGENTID_SYSTEM_ID!,
telemetry: createAgentIdTelemetryContext({
workflowRunId,
workflowName: "Invoice review",
workflowStepName: "extract_terms",
workflowStepIndex: 0,
}),
});
const response = await secured.chat.completions.create({
model: "gpt-4o-mini",
messages,
});
await agent.logOperation({
system_id: process.env.AGENTID_SYSTEM_ID!,
workflow_run_id: workflowRunId,
event_type: "tool",
tool_name: "send_slack_notification",
client_event_id: createAgentIdCorrelationId(),
});
Common Mistakes
- Do not set a random
workflowRunIdon every normal chatbot call. That creates unnecessary workflow rows. - Do not reuse one
clientEventIdfor every step in a workflow. Use one id per protected provider call or operation event. - Do not call
guard()once per historical chat message. Protect the exact assembled provider payload once. - Do not log only a masked copy after a raw provider call. The model already saw the unprotected data.
Recommended Implementation Contract
For docs, code reviews, and enterprise rollout checks, use this wording:
If the request is one provider call, integrate it as a simple LLM call and omit
workflowRunId.
If the request is a business run with multiple steps, create one workflowRunId
at the start of the run, pass it to every LLM/tool/delivery event, and keep a
separate clientEventId for each protected event.