Data Model and Schema
Reference for OpenTelemetry span names, attributes, status, outcome, tokens, cost, and the legacy JSON records of older SDKs.
This page is for developers who read ArmorIQ trace data, build reports on it, or check what a span can hold. You get the span names, the attributes that ArmorIQ stores, and how status, outcome, tokens, and cost work.
ArmorIQ receives OpenTelemetry spans over OTLP and stores them in a normalized form. Older SDKs used a different JSON record. Its fields are at the end of this page under Legacy JSON records.
Identifiers
| Identifier | Format | Notes |
|---|---|---|
| Trace ID | 32 lower-case hexadecimal characters | The standard OpenTelemetry trace ID. ArmorIQ keeps it unchanged. |
| Span ID | 16 lower-case hexadecimal characters | The standard OpenTelemetry span ID |
| Session ID | UUID | The attribute armoriq.session_id. The SDK uses a valid session_id that you pass, then a valid context_id, then a new UUID. |
| Agent ID | Free-form string | The attributes armoriq.agent_id and gen_ai.agent.name |
| Conversation, workflow, and ticket IDs | Safe identifier strings | armoriq.conversation_id, armoriq.workflow_id, armoriq.ris_ticket_id |
| Plan and plan item IDs | Safe identifier strings | armoriq.intent_plan_id, armoriq.intent_plan_item_id, with armoriq.intent_plan_item_ordinal, armoriq.intent_plan_item_status, and armoriq.intent_plan_item_count |
| Tool call ID | Safe identifier string | armoriq.tool.call_id. The policy span and the tool span of one call share it. |
ArmorIQ never takes the organization, product, or user from span attributes. It takes them from your API key.
In the dashboard, open a trace by its ArmorIQ trace ID or by its 32-character OpenTelemetry trace ID. You can also look up a trace by session, conversation, workflow run, agent, product, or ticket.
Span names and operation categories
The SDKs use these span names:
| Span name | Meaning |
|---|---|
armoriq.agent.run | The root span of a session |
armoriq.intent.plan | The plan that the agent made |
armoriq.policy.evaluate | One policy decision |
gen_ai.chat | One model call |
armoriq.tool | A tool call |
mcp.execute | An MCP tool call |
bash.execute, cli.execute, command.execute | Shell and command work |
armoriq.approval.wait | A wait for a human approval |
armoriq.delegation.wait | A wait for a delegated decision |
Use the attribute armoriq.operation.category to group and filter operations. A span name is only a hint. One SDK can merge command spans, and another can keep bash.execute, cli.execute, and command.execute apart. The category values are tool, mcp, bash, cli, command, approval, delegation, and policy.
Attributes that ArmorIQ stores
ArmorIQ keeps a fixed list of attributes. It drops any other attribute and does not reject the span. SDKs can send more attributes than the list, such as the model finish reason, but ArmorIQ does not store them.
| Group | Attributes |
|---|---|
| Resource | service.name, service.version, service.namespace, deployment.environment, deployment.environment.name, telemetry.sdk.language, telemetry.sdk.name, telemetry.sdk.version, cloud.provider, cloud.region, host.name, os.type |
| Model (GenAI) | gen_ai.system, gen_ai.request.model, gen_ai.response.model, gen_ai.operation.name, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, gen_ai.usage.cache_read_tokens, gen_ai.usage.cache_write_tokens |
| Agent and tool | gen_ai.agent.name, gen_ai.tool.name, armoriq.tool.name, armoriq.tool.type, armoriq.tool.outcome, armoriq.mcp.server, armoriq.tool.call_id, armoriq.operation.category |
| Policy | armoriq.policy.id, armoriq.policy.name, armoriq.policy.version, armoriq.policy.decision, armoriq.policy.rule_id, armoriq.policy.reason_code, armoriq.policy.enforcement_action, armoriq.policy.default_action, armoriq.policy.source |
| Execution | process.executable.name, armoriq.command.exit_code, armoriq.execution.status, armoriq.execution.retry_count, armoriq.execution.timeout, armoriq.execution.killed, armoriq.output.stdout_bytes, armoriq.output.stderr_bytes, armoriq.output.truncated, error.type |
| Approval, delegation, task | armoriq.approval.outcome, armoriq.approval.wait_ms, armoriq.delegation.id, armoriq.delegation.outcome, armoriq.task.outcome, gen_ai.task.outcome |
| Capture diagnostics | armoriq.observability.capture.mode, armoriq.observability.policy.revision, armoriq.observability.native_span.state, armoriq.observability.redaction.applied, armoriq.observability.content.truncated, and a state and reason_code for each content field |
| Content (gated) | armoriq.content.input, armoriq.content.output, armoriq.content.tool_arguments, armoriq.content.tool_result, armoriq.content.error_summary, armoriq.content.truncated. See What data is captured. |
ArmorIQ also adds armoriq.observability.capture.mode and armoriq.observability.policy.revision to every stored span, so that you can see which policy applied.
Some values can only be certain safe strings. For example, armoriq.operation.category accepts only the values above, and a free-text value that looks sensitive is dropped.
Status, outcome, and policy decisions
ArmorIQ keeps three things apart. Do not read one as another.
| Question | Where to look |
|---|---|
| Did the technical work succeed? | The span status, and armoriq.execution.status |
| Did the agent reach its goal? | armoriq.task.outcome. The values are completed, failed, denied, abandoned, partially_completed, and unknown. |
| What did policy decide? | armoriq.policy.decision and armoriq.policy.enforcement_action on the policy span |
- A policy span for a deny or a hold ends with a normal span status. Only an error while the policy ran makes it an error. This keeps a policy denial apart from a policy execution failure.
- When a run ends, the SDK sets the task outcome from the run status:
okbecomescompleted,errorbecomesfailed,deniedbecomesdenied, andcancelledbecomesabandoned. unknownmeans that the agent did not report how the task ended. The dashboard shows it as Unknown.error.typeholds only the class name of an error. Stack traces are never stored.
Timing and cost
- Timing. A span has a start time and an end time. The SDK marks how it got them: measured in a start and end hook, reported by the caller, or recorded after the fact. ArmorIQ does not trust a timing label that an SDK sends. It sets its own label, and the dashboard shows timing it cannot check as "client-reported" or "Legacy timing—not verified".
- Tokens. Model spans carry input, output, cache-read, and cache-write token counts.
- Cost. ArmorIQ computes model cost when it receives the span, from its own price catalog. It ignores a cost that an SDK sends. The current Python SDK (0.8.17) carries no local price table. When no cost is known, the dashboard shows Not reported.
- Cost is best-effort and is not billing-grade.
Session totals, trace-list totals, summary metrics, and model- or session-grouped token analytics add up the tokens of model spans. Backend aggregations scan at most 50,000 rows. Results at the cap are approximate and produce a server warning.
Completeness, sampling, and mapper version
Every stored trace carries quality information. The dashboard and the API return it:
| Field | Meaning |
|---|---|
completenessStatus and completionReason | Whether the trace is complete, partial, in progress, timed out, unknown, or not applicable. See Incomplete traces. |
expectedPlanItems, terminalPlanItems, missingParentCount | The counts that completeness uses |
partial | true when the trace is not complete, or when ArmorIQ capped the spans or plan items that it shows |
redactionStates | The number of spans in each redaction state, such as metadata_only and redacted_content |
| Capture | The capture mode and policy revision that applied to the trace |
| Sampling | retained, sampled_in, or sampled_out, with a rate and a reason. ArmorIQ always retains traces with an error, a denial, an approval, a delegation, or a policy decision. |
| Mapper version | otel/v1 for OTLP spans, and armor-sdk/v1 for the legacy JSON route |
Sensitive content
Prompts, completions, tool arguments, and tool results are not stored in the default metadata mode. In enhanced and debug, they are stored only after redaction, cut to a size limit, and only when your organization allows content capture. Secrets, credentials, authentication headers, and personal identifiers are removed in every mode.
Redaction finds patterns. It cannot find every secret. Record only what you need, do not put secrets in prompts or tool arguments, and limit access to the organization-scoped read API and dashboard. See What data is captured for the rules.
Legacy JSON records
Older SDKs sent sessions, traces, and spans as a JSON record to POST /observability/spans. ArmorIQ still accepts that route for a limited time, and it stores those records next to OpenTelemetry traces. See Migrate from the legacy recorder. The fields below describe that record. The current SDKs (Python 0.8.17, TypeScript 0.8.23) do not send it.
Session
└── Trace
├── span
├── policy_call
├── generation
└── eventAll IDs used for a trace, span, parent span, session, or user must be UUIDs when present. agentId is a free-form string.
Session
A session groups a continuous agent run or conversation. The backend creates or updates the session when it ingests a trace with a session ID.
| Field | Type | Meaning |
|---|---|---|
id | UUID | Stable session identifier |
orgId | UUID | Organization resolved from the API key |
userId | UUID or null | User identity when the producer has a UUID |
agentId | string or null | Agent identifier; not restricted to UUID format |
product | string or null | Producer identity, such as armoriq-sdk, armorcodex, or armorclaude |
startedAt | ISO 8601 datetime | Earliest trace start recorded for the session |
lastActivityAt | ISO 8601 datetime | Latest trace activity recorded for the session |
traceCount | integer | Number of accepted traces in the session |
totalInputTokens | integer | Sum from accepted generation spans |
totalOutputTokens | integer | Sum from accepted generation spans |
totalCacheReadTokens | integer | Sum of reported cache-read tokens |
totalCacheWriteTokens | integer | Sum of reported cache-write tokens |
totalCostUsd | number | Sum of reported or SDK-estimated generation cost |
status | string | Current session status; current values are active, completed, expired, and error |
The ingest path currently creates sessions with active status. Status changes require another backend lifecycle path; do not infer completion solely from the absence of new traces.
Trace
A trace is the top-level record for one IAP plan or logical operation.
| Field | Type | Required | Meaning |
|---|---|---|---|
id | UUID | Yes | Trace identifier and duplicate-ingest key |
sessionId | UUID or null | No | Parent session |
name | string, 1–200 chars | Yes | Operation name, commonly iap.plan |
startTime | ISO 8601 datetime | Yes | Trace start |
endTime | ISO 8601 datetime or null | No | Trace end |
durationMs | non-negative integer or null | No | Total trace duration |
status | ok, error, or denied | Yes | Terminal trace status |
userId | UUID or null | No | User identity |
agentId | string or null | No | Agent identity |
attributes | object | Yes | Producer-defined trace metadata |
tags | string array | Yes | Search and summary labels |
The backend adds product and source to trace-detail responses. Trace-list and session-detail responses also add token totals, cost, span count, error count, and input/output previews.
Span
A span is one observation inside a trace.
| Field | Type | Required | Meaning |
|---|---|---|---|
id | UUID | Yes | Span identifier |
parentSpanId | UUID or null | No | Parent span; null means a root-level span |
sessionId | UUID or null | No | Denormalized session identifier |
kind | span kind | Yes | Selects the attribute schema |
name | string, 1–200 chars | Yes | Operation name |
startTime | ISO 8601 datetime | Yes | Span start |
endTime | ISO 8601 datetime or null | No | Span end |
durationMs | non-negative integer or null | No | Span duration |
status | ok, error, or denied | Yes | Span status |
attributes | object | Yes | Kind-specific attributes |
Spans are returned as a flat list. Build the tree by matching each parentSpanId to another span's id. A missing or null parent is a root.
Span kinds and attributes
Attribute objects include a kind field matching the span's kind. Producers may add extra attributes; the backend preserves them.
span
Generic operation, commonly a tool call, report, dispatch, delegation, or container operation.
| Attribute | Type |
|---|---|
kind | span |
toolName | optional string |
toolInput | optional JSON value |
toolOutput | optional JSON value |
errorMessage | optional string |
Some producer-created generic spans use additional keys such as input, output, status, trustId, or fired.
policy_call
Policy evaluation and its enforcement result.
| Attribute | Type |
|---|---|
kind | policy_call |
policyId | string or null |
policyName | string or null |
policyHash | string or null |
policyVersion | string or null |
decision | allow, deny, hold, or ask |
matchedRuleId | string or null |
dataClasses | string array |
reason | string or null |
input | JSON value |
output | JSON value |
source | native, sdk-local, sdk, proxy, opa, or opa_fallback |
enforcementAction | allow, allow_log, hold, block, or null |
obligations | JSON value |
delegationId | string or null |
decision describes the policy result. enforcementAction describes what the integration did with that result.
SDK recorders map allow to span status ok. They map deny, hold, and ask to span status denied.
generation
Model call and token usage.
| Attribute | Type |
|---|---|
kind | generation |
model | string |
inputTokens | non-negative integer |
outputTokens | non-negative integer |
cacheReadTokens | non-negative integer or null |
cacheWriteTokens | non-negative integer or null |
costUsd | non-negative number |
prompt | string or null |
completion | string or null |
finishReason | string or null |
event
Point-in-time annotation.
| Attribute | Type |
|---|---|
kind | event |
message | string |
level | info, warn, or error |
An event's level is the event severity. Current SDK recorders keep the event span's lifecycle status as ok, including for level: "error"; use the attributes and surrounding trace status when interpreting an error event.
Example trace
{
"id": "11111111-1111-4111-8111-111111111111",
"sessionId": "22222222-2222-4222-8222-222222222222",
"name": "iap.plan",
"startTime": "2026-07-17T09:00:00.000Z",
"endTime": "2026-07-17T09:00:00.240Z",
"durationMs": 240,
"status": "ok",
"userId": null,
"agentId": "support-agent",
"attributes": {
"input": "Find the account owner"
},
"tags": ["armoriq-sdk", "search_contacts", "allowed"],
"spans": [
{
"id": "33333333-3333-4333-8333-333333333333",
"parentSpanId": null,
"sessionId": "22222222-2222-4222-8222-222222222222",
"kind": "policy_call",
"name": "policy.evaluate",
"startTime": "2026-07-17T09:00:00.010Z",
"endTime": "2026-07-17T09:00:00.030Z",
"durationMs": 20,
"status": "ok",
"attributes": {
"kind": "policy_call",
"policyId": "contacts-read",
"policyName": "Contacts read policy",
"policyHash": null,
"policyVersion": null,
"decision": "allow",
"matchedRuleId": "allow-support",
"dataClasses": [],
"reason": "Support agents may read contacts",
"input": { "toolName": "search_contacts" },
"output": null,
"source": "sdk",
"enforcementAction": "allow",
"obligations": null,
"delegationId": null
}
}
]
}Tags and previews
SDK trace summaries can derive tags from the product, tool names, and the policy outcome, such as allowed, blocked, or mixed. Tags are producer-supplied and are not guaranteed on every trace.
Trace-list previews prefer trace.attributes.input and trace.attributes.output. If those are absent, the backend falls back to input/output on a root span when available.
Next steps
Migrate From the Legacy Recorder
Move an agent from the legacy recorder to OpenTelemetry in the current SDK releases (Python 0.8.17, TypeScript 0.8.23). Learn what was removed, what to change, and why there is no runtime fallback to the old path.
Backend API Reference
Send OTLP traces, read the SDK policy lease, manage organization controls, and query organization-scoped sessions, traces, policies, summaries, and tokens.