Observability

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

IdentifierFormatNotes
Trace ID32 lower-case hexadecimal charactersThe standard OpenTelemetry trace ID. ArmorIQ keeps it unchanged.
Span ID16 lower-case hexadecimal charactersThe standard OpenTelemetry span ID
Session IDUUIDThe attribute armoriq.session_id. The SDK uses a valid session_id that you pass, then a valid context_id, then a new UUID.
Agent IDFree-form stringThe attributes armoriq.agent_id and gen_ai.agent.name
Conversation, workflow, and ticket IDsSafe identifier stringsarmoriq.conversation_id, armoriq.workflow_id, armoriq.ris_ticket_id
Plan and plan item IDsSafe identifier stringsarmoriq.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 IDSafe identifier stringarmoriq.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 nameMeaning
armoriq.agent.runThe root span of a session
armoriq.intent.planThe plan that the agent made
armoriq.policy.evaluateOne policy decision
gen_ai.chatOne model call
armoriq.toolA tool call
mcp.executeAn MCP tool call
bash.execute, cli.execute, command.executeShell and command work
armoriq.approval.waitA wait for a human approval
armoriq.delegation.waitA 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.

GroupAttributes
Resourceservice.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 toolgen_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
Policyarmoriq.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
Executionprocess.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, taskarmoriq.approval.outcome, armoriq.approval.wait_ms, armoriq.delegation.id, armoriq.delegation.outcome, armoriq.task.outcome, gen_ai.task.outcome
Capture diagnosticsarmoriq.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.

QuestionWhere 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: ok becomes completed, error becomes failed, denied becomes denied, and cancelled becomes abandoned.
  • unknown means that the agent did not report how the task ended. The dashboard shows it as Unknown.
  • error.type holds 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:

FieldMeaning
completenessStatus and completionReasonWhether the trace is complete, partial, in progress, timed out, unknown, or not applicable. See Incomplete traces.
expectedPlanItems, terminalPlanItems, missingParentCountThe counts that completeness uses
partialtrue when the trace is not complete, or when ArmorIQ capped the spans or plan items that it shows
redactionStatesThe number of spans in each redaction state, such as metadata_only and redacted_content
CaptureThe capture mode and policy revision that applied to the trace
Samplingretained, 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 versionotel/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
    └── event

All 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.

FieldTypeMeaning
idUUIDStable session identifier
orgIdUUIDOrganization resolved from the API key
userIdUUID or nullUser identity when the producer has a UUID
agentIdstring or nullAgent identifier; not restricted to UUID format
productstring or nullProducer identity, such as armoriq-sdk, armorcodex, or armorclaude
startedAtISO 8601 datetimeEarliest trace start recorded for the session
lastActivityAtISO 8601 datetimeLatest trace activity recorded for the session
traceCountintegerNumber of accepted traces in the session
totalInputTokensintegerSum from accepted generation spans
totalOutputTokensintegerSum from accepted generation spans
totalCacheReadTokensintegerSum of reported cache-read tokens
totalCacheWriteTokensintegerSum of reported cache-write tokens
totalCostUsdnumberSum of reported or SDK-estimated generation cost
statusstringCurrent 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.

FieldTypeRequiredMeaning
idUUIDYesTrace identifier and duplicate-ingest key
sessionIdUUID or nullNoParent session
namestring, 1–200 charsYesOperation name, commonly iap.plan
startTimeISO 8601 datetimeYesTrace start
endTimeISO 8601 datetime or nullNoTrace end
durationMsnon-negative integer or nullNoTotal trace duration
statusok, error, or deniedYesTerminal trace status
userIdUUID or nullNoUser identity
agentIdstring or nullNoAgent identity
attributesobjectYesProducer-defined trace metadata
tagsstring arrayYesSearch 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.

FieldTypeRequiredMeaning
idUUIDYesSpan identifier
parentSpanIdUUID or nullNoParent span; null means a root-level span
sessionIdUUID or nullNoDenormalized session identifier
kindspan kindYesSelects the attribute schema
namestring, 1–200 charsYesOperation name
startTimeISO 8601 datetimeYesSpan start
endTimeISO 8601 datetime or nullNoSpan end
durationMsnon-negative integer or nullNoSpan duration
statusok, error, or deniedYesSpan status
attributesobjectYesKind-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.

AttributeType
kindspan
toolNameoptional string
toolInputoptional JSON value
toolOutputoptional JSON value
errorMessageoptional 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.

AttributeType
kindpolicy_call
policyIdstring or null
policyNamestring or null
policyHashstring or null
policyVersionstring or null
decisionallow, deny, hold, or ask
matchedRuleIdstring or null
dataClassesstring array
reasonstring or null
inputJSON value
outputJSON value
sourcenative, sdk-local, sdk, proxy, opa, or opa_fallback
enforcementActionallow, allow_log, hold, block, or null
obligationsJSON value
delegationIdstring 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.

AttributeType
kindgeneration
modelstring
inputTokensnon-negative integer
outputTokensnon-negative integer
cacheReadTokensnon-negative integer or null
cacheWriteTokensnon-negative integer or null
costUsdnon-negative number
promptstring or null
completionstring or null
finishReasonstring or null

event

Point-in-time annotation.

AttributeType
kindevent
messagestring
levelinfo, 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

On this page