Correlation IDs
turn_id, message_id, thread_id, block_id, and span fields on TraceEvent for UI routing and nested runs.
Correlation IDs
Every TraceEvent carries a standard envelope plus optional correlation fields. Use them to route streaming tokens to the right chat bubble, group tool events under the LM iteration that spawned them, and scope memory writes to a conversation thread.
Standard envelope
Assigned by Tracer.emit (callers should not pre-populate these — the tracer overwrites them):
| Field | Description |
|---|---|
run_id | Stable id for the entire run (tracer constructor or OTelTracer option) |
event_id | UUID per event |
seq | Monotonic counter starting at 0 |
span_id / parent_span_id | Nested span tree for sub-agents and LM iterations |
depth | Nesting depth (0 = root agent) |
principal | Agent id that produced the event |
ts | ISO-8601 timestamp |
Per-iteration IDs
The runner mints correlation ids at the top of each LM iteration and stores them on RunContext so downstream emissions inherit stable linkage:
| Field | Scope | Description |
|---|---|---|
turn_id | Per LM iteration | UUID shared by lm_call_start, token, lm_call, tool, step, and other events in the same loop iteration. Reuses the LM span id. |
message_id | Per assistant message | UUID for the assistant message produced by the iteration. Tokens and the closing lm_call share this id. |
block_id | Per token stream segment | UUID per contiguous run of the same chunk_kind on token events (text vs reasoning vs tool-call deltas). |
thread_id | Per conversation | Caller-scoped thread from RunOptions.threadId / Maniac.chat({ threadId }). Propagated through nested runs and stamped on memory events. |
All four optional fields are nullable — consumers that do not need routing can ignore them.
import { runAgentStream } from "@maniac-ai/agents";
const byMessage = new Map<string, string>();
for await (const env of runAgentStream(spec, "Hello", { threadId: "support-42" })) {
if (env.type !== "event" || env.event.kind !== "token") continue;
const { message_id, turn_id, thread_id, delta } = env.event;
if (message_id) {
byMessage.set(message_id, (byMessage.get(message_id) ?? "") + (delta ?? ""));
}
}thread_id propagation
Set threadId on runAgent / runAgentStream / Maniac.chat options. Nested sub-agent runs inherit the parent's thread_id. Background tasks default to bg:<taskId> when no thread is supplied.
Memory stores (ConversationStore, ObservationStore, WorkingMemoryStore) namespace records by (agent_id, thread_id) under thread scope.
Nested runs
Sub-agent and parallel fan-out events carry depth > 0, their own span_id, and parent_span_id pointing at the enclosing agent span. principal identifies the inner agent id; turn_id still reflects the root iteration that triggered the nested call when emitted from within that iteration's tool handler.
Tools that call getActiveRunContext() read the active turn_id, message_id, and thread_id for custom trace emissions.
JSON Schema
The full TraceEvent shape is published at @maniac-ai/agents/trace-event.schema.json (also in dist/ after build) for Python validators, fixture generators, and IDE tooling.
OTel mapping
OTelTracer reuses maniac span_id / parent_span_id for OpenTelemetry parent context. See OTelTracer.