Trace format (public contract)
This page describes the public trace format for Maida (spec_version: "0.2"). Traces use OpenTelemetry spans as the internal representation and are stored locally as JSONL span records plus a run metadata file (meta.json). The format is a public contract for local tooling and integrations.
Versioning: The trace format is versioned independently from the package version via spec_version (currently "0.2"). All Maida releases that use spec_version "0.2" share the same public trace contract. Additive changes (new optional fields, new event types, new span attributes) may be introduced without a spec version bump. Breaking changes will result in a new spec_version.
Run storage layout
Maida stores local runs under the configured data directory. The default is ~/.maida, and callers can override it with MAIDA_DATA_DIR or config. The canonical per-run directory is <data_dir>/runs/<trace_id>/, where <trace_id> is the lowercase 32-hex-character OpenTelemetry trace ID.
<data_dir>/
runs/
<trace_id>/
meta.json
spans.jsonl
meta.json and spans.jsonl are the required files for a completed spec_version: "0.2" run directory:
meta.json- run metadata. It may be created while the run is active and is finalized when the root span ends.spans.jsonl- append-only span log. Each non-empty line is one serialized OpenTelemetry span JSON object.
Active runs are provisional: a viewer or external tool may observe status: "running", ended_at: null, duration_ms: null, or a partially populated spans.jsonl while the process is still executing. For completed runs, consumers should require both files, use meta.json.status to distinguish ok from error, and tolerate additive files that are not part of this public contract.
- Ordering: Spans in
spans.jsonlare in export order; usestart_timefor logical ordering. - Span hierarchy: Root span (no
parent_span_id) represents the run itself; child spans represent LLM calls, tool calls, state updates, warnings, and errors.
The CLI still uses the user-facing argument name RUN_ID in several commands for compatibility; in current storage that value resolves to a full OTel trace_id, and short prefixes are accepted when they uniquely match a run.
Files and readers
| Path or payload | Required? | Stable for external tools? | Notes |
|---|---|---|---|
runs/<trace_id>/meta.json |
Yes | Yes | Run discovery and summary metadata. The file may exist with status: "running" before the run finishes. |
runs/<trace_id>/spans.jsonl |
Yes | Yes | Append-only OTel span records. Read one JSON object per line and ignore fields you do not understand. |
maida export JSON |
Optional generated artifact | Yes | Portable single-file view with spec_version, run, and projected events. |
Viewer API /api/runs/{trace_id}/spans |
Runtime API | Yes | Returns spec_version, trace_id, raw spans, and projected events. |
Temporary files such as .meta.json.<pid>.tmp |
No | No | Internal atomic-write implementation detail; do not read or depend on them. |
External tools should prefer maida export or the viewer API when they need spec_version in the response envelope. Direct local-file readers should treat the directory layout and schemas below as the storage contract and should pair them with the documented spec_version for the Maida release they target.
Redaction and truncation: All span attributes and event payloads written to disk pass through redaction and truncation before being written. See the configuration reference for redact, redact_keys, and max_field_bytes.
Span envelope (all spans)
Every OTel span is serialized as a single JSON object with these fields:
| Field | Type | Description |
|---|---|---|
trace_id |
string | 32-hex-character OTel trace ID |
span_id |
string | 16-hex-character OTel span ID |
parent_span_id |
string | null | 16-hex-character parent span ID, or null for root |
name |
string | Span name (e.g. model name, tool name, run name) |
kind |
string | INTERNAL, CLIENT, SERVER, PRODUCER, CONSUMER |
start_time |
string | UTC ISO8601 with microsecond precision and trailing Z |
end_time |
string | null | UTC ISO8601 with microsecond precision and trailing Z |
duration_ms |
integer | null | Duration in milliseconds |
attributes |
object | Key-value pairs (string, bool, int, float values) |
events |
array | In-span events (name, timestamp, attributes) |
status_code |
string | OK, ERROR, or UNSET |
status_description |
string | Error description when status is ERROR |
Derived event view
Consumers that need the v0.1-style event list can use the spans_to_events() projection, which projects the span tree into a flat event list with spec_version, event_type, ts, payload, and related fields. This compatibility projection is what baselines, assertions, diffs, exports, and the local viewer use when they need event-like records.
The projection is deterministic for a given span list:
- every stored span becomes one primary event-like object
- the root span becomes
RUN_START - child spans become
LLM_CALL,TOOL_CALL,STATE_UPDATE,LOOP_WARNING,ERROR, orUNKNOWNbased on documented attributes and span events - root-span
exception,state, andmaida.loop.warningspan events are surfaced as additional projected events - a synthetic
RUN_ENDevent is appended from the root span's end state - projected events are sorted by
ts
Projection rules are part of the public contract at the event-type level: Maida preserves the event types and payload shapes below for consumer-facing workflows. The exact internal helper names and private implementation modules that perform the projection are not public API.
Event types
| Type | Description |
|---|---|
RUN_START |
Run started (emitted by @trace / traced_run) |
RUN_END |
Run finished (ok or error) |
LLM_CALL |
One LLM invocation (model, prompt, response, usage) |
TOOL_CALL |
One tool invocation (name, args, result, status) |
STATE_UPDATE |
State snapshot or diff (e.g. between steps) |
ERROR |
Exception captured (type, message, stack) |
LOOP_WARNING |
Loop detection: repeated pattern in recent events |
Payload schemas by event type
RUN_START
{
"run_name": "optional string or null",
"python_version": "3.11.7",
"platform": "darwin | linux | win32",
"cwd": "/path/to/cwd",
"argv": ["script.py", "arg1"]
}
- run_name is set from:
MAIDA_RUN_NAME(env), explicit@trace("...")/@trace(name="...")ortraced_run(name="..."), or defaultpath:function - YYYY-MM-DD HH:MM. See configuration reference. - argv may contain secrets; values for options matching redact keys are redacted before write.
RUN_END
{
"status": "ok | error"
}
LLM_CALL
{
"model": "string",
"prompt": "string | object | null",
"response": "string | object | null",
"usage": {
"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0
},
"provider": "openai | anthropic | local | unknown",
"temperature": 0.0,
"stop_reason": "string | null",
"status": "ok | error",
"error": "object | null"
}
usagefields may benullif unknown.promptandresponsemay be redacted or truncated by config.- When
statusis"error",erroris an object witherror_type,message, and optionalstack(same shape as ERROR event payload).
TOOL_CALL
{
"tool_name": "string",
"args": "object | string | null",
"result": "object | string | null",
"status": "ok | error",
"error": "object | null"
}
- When
statusis"error",erroris an object witherror_type,message, and optionalstack(same shape as ERROR event payload).
STATE_UPDATE
{
"state": "object | string | null",
"diff": "object | string | null"
}
diffis optional; may be omitted if not computed.
ERROR
Error payloads use a consistent shape (same for standalone ERROR events and nested error in LLM_CALL/TOOL_CALL):
{
"error_type": "ExceptionClassName",
"message": "string",
"stack": "string | null"
}
- Use
error_type(nottype) for the exception class name. - Guardrail aborts also use
ERROR; consumers should useerror_typevalues such asGuardrailExceededorLoopAbortto distinguish intentional guardrail stops.
LOOP_WARNING
{
"pattern": "string",
"repetitions": 3,
"window_size": 6,
"evidence_event_ids": ["event_uuid_1", "event_uuid_2"]
}
- Emitted at most once per run per distinct pattern (deduplicated).
- If
stop_on_loopguardrails are enabled,LOOP_WARNINGis still written first and is then followed byERRORandRUN_END(status="error").
meta.json schema
Each run has a meta.json file in its directory. It is created as running metadata when child spans are exported and overwritten with final metadata when the root span ends. meta.json itself does not currently include a top-level spec_version; spec_version is exposed by consumer-facing envelopes such as maida export, viewer API responses, and projected event records.
All fields in this table are required keys for the current meta.json contract. Fields whose type includes null are still required keys, but may be null while a run is active or when the value is not available.
| Field | Type | Description |
|---|---|---|
trace_id |
string | 32-hex-character OTel trace ID |
run_name |
string | null | Optional run label |
started_at |
string | UTC ISO8601 with microsecond precision and Z |
ended_at |
string | null | Set when run finishes |
duration_ms |
integer | null | Total run duration in ms |
status |
string | "running" | "ok" | "error" |
counts |
object | See below |
counts object:
{
"llm_calls": 0,
"tool_calls": 0,
"errors": 0,
"loop_warnings": 0
}
There are no stable optional meta.json fields in the current contract. Future optional fields may be added without a spec_version bump, so external tools should ignore unknown fields rather than fail closed. Tools that modify metadata should preserve unknown fields.
Local meta.json does not include spec_version. The storage contract version is declared by this reference page and by public API/export/projection envelopes that include spec_version: "0.2":
GET /api/runsGET /api/runs/{trace_id}/spansGET /api/runs/{trace_id}/pathsmaida list --jsonmaida export- projected event objects returned by
spans_to_events()
Lifecycle semantics
- During a run: child spans are appended to
spans.jsonl;meta.jsonmay appear withstatus: "running"so the local viewer can discover active runs. - On run end:
meta.jsonis overwritten with finalstatus,ended_at,duration_ms, andcounts.
Versioning note
The trace format is a public contract versioned independently from the Maida package version. All releases using spec_version "0.2" share this format. Additive changes (new optional fields, new event types, new span attributes) are allowed without a spec version bump. Breaking changes (removing fields, changing types or semantics) will be accompanied by a new spec_version. The markdown reference on this page is canonical for external tooling.
Stable versus internal
External tooling may rely on:
- The
runs/<trace_id>/meta.jsonandruns/<trace_id>/spans.jsonlstorage layout. - The required fields, types, and lifecycle semantics documented on this page.
- The required span envelope keys documented on this page.
- The
spans_to_events()projection keys:spec_version,event_id,run_id,parent_id,event_type,ts,duration_ms,name,payload, andmeta. - The projected event types and payload shapes documented here.
- Public API, export, and projection envelopes that include
spec_version. - Short trace ID prefix resolution through the CLI.
External tooling should not rely on:
- Temporary atomic-write files, write timing, or filesystem implementation details beyond the documented lifecycle.
- Private Python module names, helper function names, or internal class names.
- Undocumented span attributes or event attributes staying unchanged.
- Legacy v0.1 files (
run.json,events.jsonl) for new runs.
Compatibility expectations
The public compatibility boundary starts at spec_version: "0.2". Maida does not promise full backwards compatibility for pre-v0.2 local run directories. Legacy v0.1 files (run.json, events.jsonl) may be recognized by thin compatibility readers so commands can fail with clear upgrade or migration guidance, but external tools should not treat v0.1 as a supported storage contract.
For current-format traces, readers should fail closed on malformed required files or unsupported future spec_version values, while keeping validation errors actionable and free of raw prompt, response, tool-argument, or secret payloads. Additive fields in v0.2 should be ignored unless this page documents them as required.
CLI commands that read and write runs
maida demoand instrumented SDK runs write local traces.maida listreadsmeta.jsonto discover recent runs.maida viewreads run metadata and span data through the local viewer API.maida exportreadsmeta.jsonplusspans.jsonland writes a portable JSON envelope withspec_version, run metadata, and projected events.maida baseline,maida assert, andmaida diffread trace IDs, span data, and projected events for regression checks.
Changes from v0.1
- Storage files renamed:
run.json->meta.json,events.jsonl->spans.jsonl - Run directory keyed by OTel
trace_id(32 hex chars) instead of UUIDv4run_id - Internal representation uses OTel span model with
trace_id,span_id,parent_span_idhierarchy - LLM calls use GenAI semantic convention attribute names (
gen_ai.system,gen_ai.request.model,gen_ai.usage.*) - Consumer-facing event view preserved via
spans_to_events()projection spec_versionbumped to"0.2"