Emit Maida traces without an SDK#
An external system can participate in Maida’s behavioral regression workflow by writing Maida’s native trace format directly. No Maida SDK, framework adapter, network collector, or exported event envelope is required.
A trace is one directory containing two files:
emitted-run/
meta.json
spans.jsonl
Validate it before handing it to Maida:
maida validate-trace emitted-run/
# or
maida validate-trace emitted-run/meta.json
Validation is read-only. It does not copy or install the trace. To make an
already validated trace available to maida baseline, maida diff, or the
legacy single-run maida assert compatibility interface, emit it into the configured native location
<data_dir>/runs/<trace_id>/.
Minimal completed trace#
New emitters must use the full trace version:
spec_version: "0.2.0"
Minimal meta.json:
{
"spec_version": "0.2.0",
"trace_id": "0123456789abcdef0123456789abcdef",
"run_name": "external-agent",
"started_at": "2026-08-08T10:00:00.000Z",
"ended_at": "2026-08-08T10:00:01.000Z",
"duration_ms": 1000,
"status": "ok",
"counts": {
"llm_calls": 0,
"tool_calls": 0,
"errors": 0,
"loop_warnings": 0
}
}
Minimal spans.jsonl contains one JSON object per line. A completed trace has
exactly one root span:
{"trace_id":"0123456789abcdef0123456789abcdef","span_id":"0123456789abcdef","parent_span_id":null,"name":"external-agent","kind":"INTERNAL","start_time":"2026-08-08T10:00:00.000Z","end_time":"2026-08-08T10:00:01.000Z","duration_ms":1000,"attributes":{"maida.run_name":"external-agent"},"events":[],"status_code":"OK","status_description":""}
Trace IDs are 32 lowercase hexadecimal characters. Span IDs are 16 lowercase
hexadecimal characters and must be unique within the trace. Every span uses the
trace ID from meta.json.
Required fields#
The normative serializable shapes live in the versioned JSON Schemas in the Maida core repository:
Every meta.json field in the minimal example is required. Nullable terminal
fields remain present with null while status is running. The four count
fields are required and describe the normalized LLM calls, tool calls, errors,
and loop warnings that downstream baselines and policies evaluate.
Every span record requires the complete envelope shown above. Child spans set
parent_span_id to another span in the same completed trace. Completed traces
have one root, no duplicate span IDs, no missing parents, and no parent cycles.
Running traces may be incomplete while spans are still arriving.
LLM calls#
A child span becomes an LLM_CALL when its attributes contain
gen_ai.system or gen_ai.operation.name. Use GenAI semantic-convention usage
keys when known:
{
"gen_ai.system": "anthropic",
"gen_ai.operation.name": "chat",
"gen_ai.usage.input_tokens": 20,
"gen_ai.usage.output_tokens": 8,
"gen_ai.usage.total_tokens": 28
}
Prompts and responses are optional span events named gen_ai.user.message and
gen_ai.assistant.message, with their content in the event attributes.
Tool calls#
A child span becomes a TOOL_CALL when it has maida.tool_name. Arguments and
results are optional JSON strings in maida.tool.args and maida.tool.result
span events:
{
"maida.tool_name": "read",
"maida.status": "ok"
}
Use status_code: "ERROR", maida.error_type, maida.error_message, and the
optional maida.error_stack attribute for failed operations.
Optional enrichments#
Readers ignore additive unknown top-level fields and attribute keys. Put
emitter-specific data under a stable namespace such as emitter.*, or encode
a namespaced object in the maida.meta attribute. Do not invent new event
types for one emitter; unknown structural spans remain ordinary parent nodes
and the downstream gate stays framework-agnostic.
External emitters own redaction and truncation before writing. Do not place credentials, private keys, customer data, or unrestricted prompt/tool content in trace fields.
Main thread and Subthreads#
The single root span represents the run. Its direct action descendants form the Main thread. Represent Subthreads with the same OpenTelemetry parent topology, not a second thread schema:
run root
├── model-main
└── delegate (TOOL_CALL)
└── model-subthread
└── read (TOOL_CALL)
Here delegate.parent_span_id points to the run root,
model-subthread.parent_span_id points to delegate, and
read.parent_span_id points to model-subthread. Optional thread names belong
in namespaced metadata; parent_span_id remains the structural source of
truth.
Validation and Exit codes#
Text output is intended for a developer:
Valid Maida trace 01234567 (spec_version 0.2.0, 4 spans, status ok)
Use JSON in emitter CI:
maida validate-trace emitted-run/ --json
The result contains valid, trace_id, spec_version, status,
span_count, and diagnostics. Each diagnostic has a stable code,
location, and sanitized message; source payload values are never echoed.
Exit |
Meaning |
|---|---|
|
Trace is valid |
|
Trace content violates the schema or cross-record semantics |
|
Path is missing, unreadable, or not a run directory/ |
|
Unexpected validator failure |
Versioning and Breaking changes#
The trace schema is independent from the Maida package version. Versioned schema snapshots are immutable.
Patch releases clarify or fix serialization without changing which trace documents are accepted.
Minor releases add optional, backward-compatible fields or signals.
Major releases remove or rename fields, change required types or semantics, or change the native two-file layout. Those are Breaking changes.
Readers accept the legacy 0.2 spelling and compatible 0.2.x patch versions.
New emitters must declare 0.2.0 until a later public version is published.
See the trace schema changelog
for the published lines.