.NET API
RunEventType
Tracon.Abstractions.dllEvent types produced during a run. The user interface maps these directly onto visual elements, so the values must stay stable.
[JsonConverter(typeof(JsonStringEnumConverter<RunEventType>))]public enum RunEventTypeFields
Section titled “Fields”ChildRunCompleted = 9
A child agent run finished. Text carries the child agent’s name and
Payload the child run id.
ChildRunStarted = 8
This run started a child agent run. Text carries the child agent’s
name and Payload the child run id.
The child run’s own events are not mirrored into the root stream; only its start and end are reported. Full mirroring would multiply the event volume along the tree and send the same text to the client twice.
ChildRunTimedOut = 30
A sub-agent run passed its wait limit. Text carries the child
agent’s name; Payload is a JSON object with the child run id
(childRunId) and whether the cutoff was a hard one
(hardCutoff). When hardCutoff is false,
the sub-agent’s own cancellation was honored and its resources were
released. When true, the sub-agent ignored
cancellation and keeps running in the background; its eventual
result is discarded without producing a further event, a metric, or
an unobserved exception.
30, not inserted earlier in the list: RunEventType.Custom is 29 and
run_events.type is a smallint column — the enum’s numeric
value is what is stored. Shifting an existing member’s value would
silently reinterpret every already-persisted event of that type.
ContentBlocked = 21
An IContentGuard blocked content. Text carries the guard
name, the rule name and the direction; Payload carries the same four
facts as JSON (guard, rule, direction, action) —
the same shape RunEventType.ContentMasked writes.
The payload does not carry the blocked content. After the
event the run becomes Failed and runs.error_type is written as
content_blocked.
ContentMasked = 20
An IContentGuard masked content. Text carries the guard
name, the rule name and the direction; Payload carries the same four
facts as JSON (guard, rule, direction, action).
Neither Text nor Payload carries the masked
content — they only report THAT masking happened. When the text the
model sees differs from what the user wrote, that is an event and it cannot
stay silent (that rule: a decision that cannot be recorded is a decision
that was not taken).
Custom = 29
A consumer-defined event, written directly with
AgentRunScope.Writer.AppendAsync. CustomType (a
namespaced string, see RunEventCustomTypes) names it;
Payload carries whatever the consumer put there. Tracon
makes no claim about the payload’s shape — it is the consumer’s own
and out of scope for this contract. A consumer’s own Custom event
follows the general rule in the type’s remarks: with
TraconRunRecordingOptions.RecordToolPayloads off it still
appears in the stream but Payload is null. Only
RunEventCustomTypes.QuotaThreshold and
RunEventCustomTypes.OutputTruncated are exempt.
The escape hatch for the otherwise closed set of event types above. When a built-in event type already fits, use that instead — RunEventType.Custom is for events Tracon has no name for. A type under the RunEventCustomTypes.ReservedPrefix namespace is rejected at write time, so a future built-in custom type can never collide with a consumer’s own.
DocumentAttached = 24
A document was attached to the run through the document channel.
Text carries the document’s name; Payload carries its
size in bytes and a content hash as JSON - never the document’s
content itself.
ExecutorCompleted = 15
An executor completed. Text carries the executor id.
ExecutorFailed = 16
An executor failed. Text carries the executor id and Payload
a display-safe error text: a Tracon exception’s own message, otherwise
<Type> failed. (ref: <id>), where the id names the log
entry that holds the full exception.
A graph-level error is written with the same type; its Text is
the exception’s type name.
ExecutorInvoked = 14
An executor was invoked. Text carries the executor id.
HistoryCompacted = 10
The chat history was compacted. Text carries a short summary sentence
and Payload the before/after message and token counts.
LoopIterationCompleted = 31
One harness loop iteration was evaluated (see LoopSettings).
Text carries a one-line summary; Payload is a JSON object
with the 1-based iteration number (iteration), whether a criterion
asked for another iteration (continued), the kind of that
criterion (continuedBy, null when every criterion
was satisfied), whether it handed the agent feedback (hasFeedback),
the kind of a criterion that failed to evaluate
(failedCriterion, null otherwise), whether
the iteration ceiling is what ends the loop (ceilingReached),
whether this evaluation fails the run (failsRun, see
LoopSettings.OnExhausted and LoopSettings.OnCriterionError),
and every criterion that was evaluated, in order (criteria): its kind and its
decision (stop, continue or failed), plus the judge
(provider and model an aiJudge really called) or the reviewer
(agentName, agentVersion and the child runId of an agentReview).
The event marks an evaluated iteration, not a model
turn. The turn that reaches LoopSettings.MaxIterations is
not evaluated — there is nothing left to decide — so a loop that ends at
its ceiling writes one event fewer than it takes turns, and its last
event carries ceilingReached. The exception is
LoopOutcome.FailRun on LoopSettings.OnExhausted:
the last turn is evaluated, the events match the turns, and an
unsatisfied last evaluation carries both ceilingReached and
failsRun.
The criterion’s feedback text is deliberately absent: it is free-form text written for the model, and the run event stream is readable by the client. Its presence is reported, its content is not.
criteria answers “who decided”. Evaluation stops at the first criterion that asks
for another iteration or fails, so the list ends there; an iteration every criterion
satisfied lists them all. A criterion that reports no judge and no reviewer carries only
its kind and decision. reviewer.runId is null when the
call was refused before a run started, and reviewer.agentVersion is the version that run
recorded (1 for an agent defined in code).
31, not 30: RunEventType.ChildRunTimedOut already holds 30 and
run_events.type is a smallint column, so an existing
member’s numeric value can never be shifted.
MessageCompleted = 2
A message completed.
MessageDelta = 1
A text fragment arrived from the model. Produced only for streaming runs.
ModelFallbackUsed = 22
The primary model provider was unavailable and a
ModelBinding.Fallbacks link answered instead. Text
carries the fallback provider and model ("{provider}/{model}");
Payload carries the primary and fallback bindings and the reason
the primary was skipped.
A model switch is never silent. This event is written in addition to a span tag, not instead of it — an operator reading only the run record must still see which model actually answered.
Only the agent’s own binding writes it. A fallback on a binding the run
calls on the side (the compaction summarizer, an aiJudge judge)
does not change the run’s model, so it is logged instead.
ReasoningDelta = 23
A reasoning (thinking) delta arrived from the model. Off by default
(TraconRunRecordingOptions.RecordReasoningDeltas).
RunAwaitingInput = 19
The run stopped because it waits for a human answer. It is the last event of the stream.
Separate from RunEventType.RunCompleted and RunEventType.RunFailed: the work neither finished nor failed. On this event the user interface shows the pending request card.
Produced for two different terminal statuses, and Payload differs between
them. For a workflow closing with RunStatus.AwaitingInput, the
details already arrived on the preceding RunEventType.WorkflowRequest event and
this one carries no payload of its own. For a root run closing with
RunStatus.AwaitingApproval, Payload is a JSON array, one
entry per pending tool call: requestId, toolName, and — when a
consumer registered an IToolApprovalPresenter and it resolved
something — entityType, entityId, entityName, and
message from ToolApprovalPresentation. It never carries the
call’s raw arguments; those are read from PendingApproval.Arguments
or from the ToolApprovalRequestContent already present in the response.
For a root agent run closing with RunStatus.AwaitingInput because an MCP server
asked the user for input in the middle of a tool call, Payload is a JSON array, one
entry per suspended call: requestId, toolName and serverName. It never
carries the requested fields or the call’s arguments; the caller got them in the response.
RunCompleted = 6
The run finished successfully.
RunContinuationBlocked = 25
Automatic continuation of this interrupted run was refused. Text
carries the reason (a tool whose effect cannot be safely repeated).
Written on a run already closed RunStatus.Failed by
orphaned-run reconciliation; the row itself does not change again.
RunFailed = 7
The run ended with an error.
RunStarted = 0
The run started.
SessionWriteConflicted = 32
The run finished, but the session write that follows it lost a race and
nothing was saved to that session. Text carries the session id.
The run itself is RunStatus.Completed and that is the truthful status: the model was called, tokens were spent, an answer was produced. What failed is the session write that happens AFTER the run, and the caller was told — it received a 409.
Before this event the conflict left no trace at all in the record: the operator saw two successful runs for the same session and the losing side existed only in the response the caller already consumed. When a session owner says a message went missing, the run record now carries the evidence.
32, not 30 or 31: run_events.type is a smallint column, so
an existing member’s numeric value can never be shifted.
StructuredResponseRejected = 27
The response failed structured output validation and the run is
ending as RunStatus.Failed. Text carries the
safe rejection reason; Payload carries the same reason plus
kind, schemaName, provider and model as JSON.
Neither field carries the model’s raw response text — see IStructuredResponseValidator.
StructuredResponseRepairAttempted = 28
A bounded repair turn is about to run after a rejected structured
response. Text carries the attempt count as "{attempt}/{max}";
Payload carries the same two numbers plus kind and
schemaName as JSON.
Written once per repair round, immediately before the extra model call
it describes, and only when TraconStructuredResponseOptions.MaxRepairAttempts
is greater than zero. See RunEventType.StructuredResponseRejected, which
always precedes it: a repair round only starts after a rejection, never
on its own.
SuperStepCompleted = 13
A super-step completed. Text carries the step number and
Payload the activated executor names plus the checkpoint id, if any.
SuperStepStarted = 12
A super-step started. Text carries the step number and Payload
the names of the executors that sent messages.
ToolFailed = 5
A tool failed.
ToolInvoked = 4
A tool completed successfully. The result is in the event payload.
ToolInvoking = 3
A tool is about to be called. The arguments are in the event payload.
ToolOutputTruncated = 26
A tool result was trimmed to its byte limit. ToolName carries
the tool’s name; Text a short summary of how many bytes were
dropped and the limit. Payload carries the same information as JSON.
Neither field carries the dropped content itself — the point of
trimming is to reduce volume, and writing the discarded part back
into run_events would undo that.
WorkflowOutput = 17
The workflow produced an output. Text carries a text summary of it.
WorkflowRequest = 18
The workflow waits for an external answer (human in the loop). Text
carries the request id and Payload a JSON summary of the request.
The payload carries enough to rebuild the pending request: port id,
request id, request and response type names, and the data to display.
Pending requests are read from these events; no separate table was added.
That is why this event is one of the two exempt from
TraconRunRecordingOptions.RecordToolPayloads (see the type’s
remarks): suppressing this payload would leave the user unable to see
the question they have to answer.
WorkflowResponseApplied = 33
This run continues a workflow run by answering one of its pending human
requests. Text carries the request id; Payload is
{"sourceRunId": "...", "requestId": "..."}, the run that waited and the
request this run answers.
Written right after RunEventType.RunStarted by every run that answers a
request, whoever started it. The answer itself is not written here: it can
hold anything the answering person typed, and the run event stream is readable
by the client. Read the decision record from
GET /api/workflows/runs/{runId}/responses of the source run.
The payload is written even when RecordToolPayloads is off: it is the
only place this run names its source.
33, not a lower free number: run_events.type is a smallint
column, so an existing member’s numeric value can never be shifted.
WorkflowStarted = 11
A workflow execution started. Text carries the workflow name.
This is separate from RunEventType.RunStarted: that one reports that the
runs row was opened, this one reports that the Microsoft Agent
Framework execution engine actually took over the graph. Compilation and
validation happen in between, and an error there ends the run before the
graph ever starts.
Remarks
Section titled “Remarks”Written to JSON by name ("Code"), not by number. The
wire contract explains itself that way and survives a change in value order.
The converter sits on the type, so the format is the same everywhere without
touching the consumer’s application-wide JSON options. No enum is PERSISTED as
JSON (RunStatus and RunEventType are smallint in the database,
AgentDefinitionOrigin is rebuilt on read), so a format change does not affect
stored data.
Every member’s Payload is written only when
TraconRunRecordingOptions.RecordToolPayloads is on.
With it off the event still appears in the stream and Text still
carries its summary, but Payload is null — so a
payload described below is what the field holds when it is written, not a
promise that it is always there. Two exceptions always carry theirs,
because there the payload is the function rather than an observability
detail: RunEventType.WorkflowRequest, which is the only place a pending
human request can be read from, RunEventType.WorkflowResponseApplied, which
is the only link from an answer’s run back to the run it answered, and
RunEventType.Custom of the two notice types RunEventCustomTypes.QuotaThreshold
and RunEventCustomTypes.OutputTruncated, whose payload carries the dedup
key a client needs to act on the frame. A consumer’s own RunEventType.Custom
event is not exempt, and neither are the other reserved types
(RunEventCustomTypes.Content, RunEventCustomTypes.WorkflowEvent).