Data Model and Schema
Reference for observability sessions, traces, spans, attributes, status, tokens, and cost.
ArmorIQ stores observability as sessions containing traces, with spans forming a parent-child tree inside each trace.
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.
Token and cost behavior
- Session totals, trace-list totals, summary metrics, and model- or session-grouped token analytics aggregate
generationspans. ThegroupBy=toolendpoint instead scans genericspanrecords and sums token-shaped attributes when a producer supplies them. - The TypeScript and Python SDKs can calculate cost from a static per-model table.
- Cost is best-effort and is not billing-grade.
- An unknown model currently produces
costUsd: 0. Treat that as pricing unavailable unless you know the provider reported a measured zero cost. - A missing generation span means usage was not reported. Current backend aggregate fields are initialized to zero, so list and dashboard views cannot always distinguish this case from measured zero; inspect the raw trace for a
generationspan. - Backend aggregations scan at most 50,000 rows. Results at the cap are approximate and produce a server warning.
Sensitive content
toolInput, toolOutput, policy input and output, prompt, and completion can contain sensitive information. The shared SDK recorders do not automatically redact arbitrary values. Record only what you need, omit prompt/completion content when possible, and restrict access to organization-scoped read APIs.
Next: call the backend API or inspect spans in the dashboard.