Trace events
Side-channel HTTP endpoint for forwarding intermediate reasoning, system prompts, and assistant turns to the trace tab while a run is in flight.
The trace tab polls in-progress runs every three seconds. Posting trace events to the per-run proxy allows intermediate reasoning to appear in the trajectory timeline during execution, rather than only through messages after run completion.
Python users can use SDK helpers:
post_trace_event and the safe_* variants.
Adapters integrate these helpers into loop and result handling
automatically, so no manual shim implementation is required.
Endpoint
POST {odyssey_proxy_url}/traces/{event_type}
Authorization: Bearer <run token> # OR X-Pipelines-Run-Token: <run token>
Content-Type: application/json
<event payload (JSON object)>Reserved event_type values
| event_type | Use for |
|---|---|
| assistant_message | Intermediate assistant turn. |
| thinking | Reasoning block, including Anthropic thinking and OpenAI reasoning. |
| system_prompt | Resolved system prompt. Post once near run start. |
| custom | Any other non-reserved event payload. |
| handoff | Sub-agent to sub-agent control transfer. See Multi-agent events. |
| subagent_message | Inter-sub-agent message. See Multi-agent events. |
| subagent_final | Intermediate sub-agent output. Does not override dispatch final_response. |
Any other event_type is rejected with trace_event_type_invalid.
Payload and ordering
Add an __occurred_at ISO-8601 field to control row ordering. The trace tab sorts by this field regardless of insertion order, and falls back to proxy arrival time when absent. If two events share the same timestamp, the monotonic sequence value returned in the response is used as tiebreaker. Clock skew across parallel emitters should be managed by the caller.
All other payload fields are free-form. The trace tab renders text or content fields inline and places remaining fields under Raw payload.
Response
{ "accepted": true, "sequence": 17, "event_type": "thinking" }sequence increases monotonically within a run.
Errors
| Status | detail.error_class | Meaning |
|---|---|---|
| 400 | trace_event_type_missing / trace_event_type_invalid | Path segment is empty or not in reserved values. |
| 400 | trace_payload_invalid | Request body is not a JSON object. |
| 400 | trace_run_id_invalid | Run id encoded in token could not be parsed. |
| 401 | invalid_run_token | Token is missing, malformed, or expired. |
| 404 | trace_run_not_found | Token validated but no matching run exists. |
| 413 | none | Body exceeds 1 MiB. |
| 503 | trace_sequence_contended | Back off and retry. Run sequence allocator is contended. |
Auth and limits
- Uses the same per-run bearer as the tool-call proxy.
- Rate limit is 120 requests per minute per token.
- Body size limit is 1 MiB.
- Events can be written after run completion. Late events are still ordered correctly in trajectory.
Minimal TypeScript example
async function postTraceEvent(
proxyUrl: string,
runToken: string,
eventType: "assistant_message" | "thinking" | "system_prompt" | "custom",
payload: Record<string, unknown>,
): Promise<void> {
const res = await fetch(`${proxyUrl.replace(/\/$/, "")}/traces/${eventType}`, {
method: "POST",
headers: {
Authorization: `Bearer ${runToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ ...payload, __occurred_at: new Date().toISOString() }),
});
if (!res.ok) {
// Trace forwarding is best effort. Log and continue.
console.warn(`trace event ${eventType} failed: HTTP ${res.status}`);
}
}Recommended behavior is best-effort. A failed trace POST should produce a warning but should not fail the run. Canonical run evidence remains in agent_tool_calls for tool activity and in the final response envelope for all other outputs.