Canonical Trace Reference
Every trace Mibo stores has the same canonical structure: an array of OTel-semconv-shaped spans. This page is the reference for that structure and the attributes the runner reads. If you’re looking for how to send a trace, start at Sending Traces.
The canonical shape
Section titled “The canonical shape”interface CanonicalSpan { span_id: string; parent_span_id?: string | null; name: string; attributes?: Record<string, unknown>; start_time_unix_nano?: string; // uint64 nanoseconds, string-encoded end_time_unix_nano?: string; status?: { code?: number; message?: string }; // 1 = OK, 2 = ERROR events?: unknown[];}
interface CanonicalTrace { spans: CanonicalSpan[];}Both ingestion paths produce this. OTLP arrives in OTel envelope shape and is normalized into the canonical shape by the API; Your API posts it directly.
Identity
Section titled “Identity”Trace identity is header-only, taken from x-request-id:
- Present → create-or-replace by that id. Re-posting overwrites the previous trace.
- Absent → server-generated UUID. Always creates a new trace.
Body fields never carry identity — there is no top-level externalId field that affects routing.
Span tree
Section titled “Span tree”parent_span_id defines the tree. The root span has parent_span_id: null (or omits the field). Mibo derives trace-level values from the root when available — total duration, root HTTP status, root token usage.
Tool calls attach to their parent through gen_ai.tool.name on the child span. The dashboard renders them as nested tool_calls under the parent’s display.
Attribute conventions
Section titled “Attribute conventions”These are the keys Mibo’s assertion engine reads. Emit what you have — missing keys surface as missing instrumentation in the dashboard rather than passing or failing silently.
GenAI semconv
Section titled “GenAI semconv”| Attribute | Type | What it powers |
|---|---|---|
gen_ai.output.messages | array of { role, parts: [{ type, content }] } (or JSON-stringified) | Preferred response source. Mibo picks the last assistant message and concatenates its text parts. |
gen_ai.response.text | string | Fallback response source. Resolution: root span first, then the latest AI-orchestrator span (one carrying gen_ai.system or gen_ai.request.model), then the latest span overall. |
gen_ai.input.messages | array of { role, parts: [{ type, content }] } (or JSON-stringified) | User input shown to semantic evaluators. Mibo picks the latest user message, falling back to system, then to concatenated parts. Truncated to 4000 chars. |
gen_ai.prompt | string | Fallback input source when input.messages is absent. |
gen_ai.system / gen_ai.request.model | string | Marks the span as the AI orchestrator — biases text and input extraction toward it instead of whatever non-AI step (e.g. HTTP Request) ran last. |
gen_ai.usage.input_tokens | number | token_limit.max_input_tokens. Root first, summed across spans if absent. |
gen_ai.usage.output_tokens | number | token_limit.max_output_tokens. Root first, summed across spans if absent. |
gen_ai.usage.reasoning.output_tokens | number | Added to total when computing token_limit.max_total_tokens. |
gen_ai.tool.name | string | The span is treated as a tool call of its parent. Skipped by node_call. |
gen_ai.tool.call.arguments | JSON string or object | tool_call.expected_arguments. Matchers: exact, contains, regex, date, one_of, any. |
HTTP semconv
Section titled “HTTP semconv”| Attribute | Type | What it powers |
|---|---|---|
http.response.status_code | number | http_status. Read from the root, falls back to any descendant. |
Structural
Section titled “Structural”| Field | What it powers |
|---|---|
span.name | node_call.expected_name (case-insensitive substring match). |
Root start_time_unix_nano / end_time_unix_nano | response_time.max_ms — Mibo computes (end - start) / 1_000_000. |
parent_span_id | The tree. Children of a span with gen_ai.tool.name show up as nested tool calls. |
status.code === 2 on any span | The whole trace is marked failed. |
The span.name invariant
Section titled “The span.name invariant”span.name is the user-facing display label of the step:
- For an n8n workflow node: the display name you see in the n8n canvas.
- For a Flowise component: the component’s label in the chatflow editor.
- For an OTel-instrumented service: whatever your tracer uses, ideally a stable human-readable identifier.
The node_call assertion does case-insensitive substring matching against this — what you see in your tooling is what you write in your assertion. Never use internal ids or class names.
This invariant is enforced by the n8n Testing Node, the Flowise template, and is documented for hand-rolled clients in Your API.
Vendor-prefixed attributes
Section titled “Vendor-prefixed attributes”You may emit your own attributes for organizational or rendering purposes:
n8n.node.type,n8n.node.status,n8n.node.output,n8n.node.parameters— emitted by the n8n Testing Node.flowise.component.type,flowise.component.status,flowise.component.output— emitted by the Flowise template.- Anything else you want.
The dashboard renders these generically alongside the span. Assertions ignore them unless you reference them explicitly.
Minimal example
Section titled “Minimal example”{ "spans": [ { "span_id": "root", "name": "Customer Support Agent", "attributes": { "gen_ai.response.text": "We're open Monday to Friday, 9am to 5pm." }, "status": { "code": 1 } } ]}This is enough for semantic and response_regex assertions to evaluate against the response.
Example with a tool call
Section titled “Example with a tool call”{ "spans": [ { "span_id": "root", "name": "Booking Agent", "start_time_unix_nano": "1717000000000000000", "end_time_unix_nano": "1717000001500000000", "attributes": { "gen_ai.response.text": "Booked for tomorrow at 9am.", "gen_ai.usage.input_tokens": 120, "gen_ai.usage.output_tokens": 80, "http.response.status_code": 200 }, "status": { "code": 1 } }, { "span_id": "tool-1", "parent_span_id": "root", "name": "create_booking", "attributes": { "gen_ai.tool.name": "create_booking", "gen_ai.tool.call.arguments": "{\"date\":\"2026-03-09\",\"time\":\"09:00\"}" }, "status": { "code": 1 } } ]}Lets you write:
{ "target": "node_call", "condition": "MUST_CALL", "expected_name": "Booking Agent", "expected_tool_calls": [ { "condition": "MUST_CALL", "expected_name": "create_booking" } ]}