Span spec
The span taxonomy behind every Swirls run. What each span represents, how the trace tree nests, and the identity that ties every span to a project.
What it is. The specification for the spans that make up a Swirls run. Every hosted workflow execution and every root agent session is one OpenTelemetry trace, and that trace is a tree of spans of a small, fixed set of kinds. This page defines that set: the span names, what each one represents, how they nest, and the identity every span carries so it routes to the right project.
Use it when you read a trace in Swirls Cloud and want to know what a span is, when you map Swirls spans into your own backend after exporting, or when you query the trace store and need to know which span a metric lives on.
For the full list of attribute keys on each span, see the Attribute schema. For the product view of traces, see Traces.
The trace model
A trace is one run. The root span is the run; everything the run does nests beneath it.
- A workflow execution roots its trace at
swirls.graph.execution. Nodes, agent turns, model calls, tools, and sandbox processes are spans inside it. - A root agent session (an agent reached over the API or a chat channel, with no workflow around it) roots its trace at the agent turn span. The model calls and tools it runs nest beneath that.
Spans export when they end, so a trace fills in as the run proceeds. A zero-duration swirls.trace.complete sentinel marks the point where no more spans are coming.
Span kinds
Every span carries swirls.span.kind, a structural classification derived from the span name. It makes the shape of the tree explicit instead of inferred, and it is the key a downstream export vendor maps to its own span classification. There are five values.
swirls.span.kind | Span names that classify as this kind |
|---|---|
workflow | swirls.graph.execution |
agent | gen_ai.invoke_agent, gen_ai.create_agent |
llm | gen_ai.chat, the AI SDK ai.* model spans |
tool | swirls.sandbox.exec, swirls.workflow.invoke, gen_ai.execute_tool, ai.toolCall |
internal | swirls.trace.complete |
swirls.node.execution is structural and is identified by its name rather than a kind. The kind is stamped centrally as each span starts, so spans Swirls does not author (the AI SDK ai.* and gen_ai.* spans) are classified the same way the ones it does author are.
Span types
swirls.graph.execution
The workflow run. The root span of a workflow trace, and the owner of the run's outcome. It carries the workflow identity (swirls.workflow.id, swirls.workflow.label), the trigger that started the run (swirls.trigger.type, swirls.trigger.id), and the final swirls.execution.status of completed or failed. When the run is itself a nested workflow, swirls.parent.execution.id links it to its caller. Emitted by the executor.
swirls.node.execution
One node in the workflow. Parents under the graph span, so a run reads as a tree of nodes. Carries swirls.node.id and swirls.node.name, plus the node's policy-gated input and output (swirls.node.input, swirls.node.output) and any operation metadata the node returns (swirls.node.meta). An ai node also stamps the prompt and response it sent to the model (swirls.ai.prompt, swirls.ai.response) and token usage. An agent node accumulates the agent transcript onto swirls.agent.messages when it ends. Emitted by the executor.
gen_ai.invoke_agent
One agent turn. On the workflow path it nests under the agent node; on the chat or API path it is the trace root. Carries the agent name and the requested model under the GenAI conventions, the turn's configuration (swirls.agent.max_steps, swirls.agent.output_mode, swirls.agent.tool_names), the turn's outcome rollups (swirls.agent.step_count, swirls.agent.tool_call_count), and full token usage. When a content policy is set, it also carries the durable, policy-gated LLM input and output. As a root turn it owns swirls.execution.status and emits the completion sentinel. Emitted by the agent host.
gen_ai.chat and ai.*
A single model call. Emitted by the Vercel AI SDK as part of an agent turn, one per request to the model. Carries the GenAI conventions for model, finish reason, and per-call token usage. Classified as kind llm. The AI SDK also emits gen_ai.input.messages and gen_ai.output.messages on these spans; those bodies ride a default-off path and the collector strips them, so durable message capture happens on the owning agent turn span instead.
ai.toolCall and gen_ai.execute_tool
A tool invocation. ai.toolCall is emitted by the AI SDK for each tool the model calls; gen_ai.execute_tool marks an agent invoking a workflow as a tool. Both classify as kind tool and carry the tool name and call id.
swirls.sandbox.exec
A process run inside a sandbox. Emitted by the agent host when an agent runs a command. The program name (swirls.sandbox.program), the tool name (swirls.tool.name), and the exit code (swirls.sandbox.exit_code) are always recorded. The full command text (swirls.sandbox.command) can carry secrets, so it is captured only under the project content policy.
swirls.workflow.invoke
A nested workflow called as a tool by an agent. Carries gen_ai.operation.name of execute_tool, the tool name, and swirls.child.execution.id, which links to the root span of the nested workflow's own trace. Emitted by the executor.
swirls.trace.complete
The completion sentinel. A zero-duration span emitted once per trace, as the root ends, carrying swirls.trace.complete set to true and kind internal. Because spans export on end, a consumer that watches for this span knows the trace tree is finished and no more spans are coming.
swirls.review.gate is not a span. A review request is recorded as a span event on the graph span, so a human-in-the-loop gate that stays open for a long time does not hold a span open across the wait.
Identity and scope
Every span in a run carries the same tenant and execution identity, so the whole tree routes to one project and one execution even when Swirls did not author the span.
| Attribute | What it scopes the span to |
|---|---|
swirls.org.id | The organization. |
swirls.project.id | The project. This is the key the trace store routes and filters on. |
swirls.deployment.id | The deployment the run came from. |
swirls.workflow.id | The workflow definition. |
swirls.execution.id | The single run. |
These are not inherited down the tree by OpenTelemetry. A child span does not pick up its parent's attributes. Instead the active run sets a scope at the execution or agent-turn boundary, and a span processor stamps that scope onto each span as it starts. That is how a model call emitted by the AI SDK or a process emitted by a sandbox still carries swirls.project.id and swirls.execution.id, and so still lands in the project's trace store. The completion sentinel is stamped the same way so a project-scoped query never drops it.
Run status
Run outcome lives on one span per trace, never two.
- On a workflow run,
swirls.graph.executionownsswirls.execution.status. - On a root agent session, the root
gen_ai.invoke_agentturn owns it.
The value is completed or failed, the same vocabulary in both cases, so the Traces list reads one outcome per run. A non-root agent turn inside a workflow does not stamp status; the graph root already owns it.
Content policy
The spans above split into two classes of attribute. Identity, status, span kind, timings, names, and token usage are structure: they are always present, and they keep a trace navigable. Run I/O is content: node inputs and outputs, model prompts and completions, agent messages, and sandbox command text.
Content is gated by a per-project policy with three settings.
| Policy | What is captured |
|---|---|
full | The serialized value, truncated past a size cap. |
redacted | The shape only: object keys and value types, no leaf values. |
off | Nothing. |
The gate runs at capture time, not display time. Under redacted or off the content never reaches a span, so it cannot leak through a later trace export or a query against the trace store. The Attribute schema marks every content attribute, so you know exactly which keys the policy governs.
Correlation
Traces use W3C trace context. When the caller that triggers a run propagates traceparent into Swirls, the run's trace links to that upstream trace, so a Swirls execution sits in line with the rest of your system. A nested workflow is linked to its caller through swirls.child.execution.id on the swirls.workflow.invoke span rather than by nesting, so each execution keeps its own trace.