Skip to main content

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 workflowRunId for the whole run.
  • Every real provider call still needs its own clientEventId correlation.

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

FieldUseNotes
workflowRunId / workflow_run_idAgent workflow groupingSet only when several events belong to one business run.
workflowName / workflow_nameHuman label for the runExample: Support triage.
workflowStepId / workflow_step_idStable id for one stepOptional but useful for retries and debugging.
workflowStepName / workflow_step_nameHuman label for one stepExample: initial_prompt, search_docs, send_email.
workflowStepIndex / workflow_step_indexStep orderingUse zero-based order when possible.
clientEventId / client_event_idOne protected unit of workDo not reuse one id for the whole workflow.
event_subtype: "prompt_preflight_evaluated"Guard/preflight eventCreated by official wrappers before provider execution.
runtime_surfaceIntegration surfaceExamples: vercel_ai_sdk_guard, vercel_ai_sdk_wrapper, openai_sdk_guard.

Decision Table

ScenarioSet workflowRunId?Dashboard intent
User sends one chat message to one LLM callNoOne compact LLM row.
API route calls streamText() once for a chatbot responseNoOne compact LLM row.
User prompt starts an agent with tools and final answerYesWorkflow row plus prompt audit row.
Background job summarizes 100 tickets independentlyUsually no per ticket, unless each ticket has a multi-step runKeep unrelated jobs separate.
LangChain/LlamaIndex-style chain with retriever, tools, and model callsYesGroup all steps into one workflow.
Manual masked logging after a raw provider callInvalidProtect 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 workflowRunId on every normal chatbot call. That creates unnecessary workflow rows.
  • Do not reuse one clientEventId for 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.

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.