# RunEventWriter

> Tracon documentation. Tracon is a .NET package family that adds a control
> plane on Microsoft Agent Framework, published as NuGet packages that run
> inside your own ASP.NET Core application.
>
> Page: https://tracon.dev/api/tracon.runeventwriter/
> Summary: RunEventWriter: Writes the events of a single run into IRunStore and produces the sequence number.

<div class="api-provenance">Namespace Tracon · Assembly <code>Tracon.Core.dll</code></div>

Writes the events of a single run into [IRunStore](/api/tracon.irunstore/) and
produces the sequence number.

```csharp
public sealed class RunEventWriter
```

<dl class="api-relations"><dt>Inheritance</dt><dd><a href="https://learn.microsoft.com/dotnet/api/system.object">object</a> ← <a href="/api/tracon.runeventwriter/">RunEventWriter</a></dd><dt>Inherited Members</dt><dd><a href="https://learn.microsoft.com/dotnet/api/system.object.gettype">object.GetType()</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.tostring">object.ToString()</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object)">object.Equals(object?)</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object)">object.Equals(object?, object?)</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.referenceequals">object.ReferenceEquals(object?, object?)</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.gethashcode">object.GetHashCode()</a></dd></dl>

## Remarks

**The sequence number is produced by a single writer, one event at a
time.** Numbering an event, writing it to the store and handing it to
every [IRunEventSink](/api/tracon.iruneventsink/) happen as one step, and the next event
waits for it. A run writes from more than one place at once (concurrent tool
calls, a child run writing into its parent's writer, a workflow's executors
next to its event pump), so without that the store and the sinks could see
event 6 before event 5. This keeps the event order deterministic and
guarantees that live streaming and replay produce the same result.

A store or a sink therefore must not call [RunEventWriter.AppendAsync](/api/tracon.runeventwriter/#Tracon_RunEventWriter_AppendAsync_Tracon_RunEventDraft_System_Threading_CancellationToken_) on the
writer whose event it is handling: that append would wait for the very event
it is part of. From a sink, such an append throws
[InvalidOperationException](https://learn.microsoft.com/dotnet/api/system.invalidoperationexception) instead of waiting, and the sink is
disabled for the run.

**Store failures do not interrupt the run.** Observability
must not break functionality. Every write failure is logged and
swallowed; once the writer hits a failure, it moves into the
[RunEventWriter.IsDisabled](/api/tracon.runeventwriter/#Tracon_RunEventWriter_IsDisabled) state and attempts no further writes for that run.

**The loss is counted, not only logged.** Every lost write
increments [TraconDiagnostics.RunRecordingFailureCounterName](/api/tracon.tracondiagnostics/#Tracon_TraconDiagnostics_RunRecordingFailureCounterName),
tagged with the tenant and the stage it was lost at, so that a hole in a
run's evidence is something a dashboard shows rather than something a
reader of logs eventually notices.

Tracon creates one writer for each run. Code that runs inside a run, such as a
tool, reads it from [AgentRunScope.Writer](/api/tracon.agentrunscope/#Tracon_AgentRunScope_Writer) of [TraconRunContext.Current](/api/tracon.traconruncontext/#Tracon_TraconRunContext_Current)
and appends its own events with [RunEventWriter.AppendAsync](/api/tracon.runeventwriter/#Tracon_RunEventWriter_AppendAsync_Tracon_RunEventDraft_System_Threading_CancellationToken_).

## Properties

### <a id="Tracon_RunEventWriter_EventCount"></a> EventCount

Gets the number of events written.

```csharp
public long EventCount { get; }
```

#### Property Value

 [long](https://learn.microsoft.com/dotnet/api/system.int64)

### <a id="Tracon_RunEventWriter_IsDisabled"></a> IsDisabled

Gets whether the writer was disabled because it hit a store failure.
A disabled writer silently does nothing.

```csharp
public bool IsDisabled { get; }
```

#### Property Value

 [bool](https://learn.microsoft.com/dotnet/api/system.boolean)

### <a id="Tracon_RunEventWriter_RunId"></a> RunId

Gets the identity of the run this writer writes to.

```csharp
public Guid RunId { get; }
```

#### Property Value

 [Guid](https://learn.microsoft.com/dotnet/api/system.guid)

### <a id="Tracon_RunEventWriter_TenantId"></a> TenantId

Gets the tenant of the run this writer writes to. Stamped onto every sub-write
as defense in depth.

```csharp
public string? TenantId { get; }
```

#### Property Value

 [string](https://learn.microsoft.com/dotnet/api/system.string)?

#### Remarks

The value is the run's own tenant, which Tracon stamps when it starts the
run ([RunStartInfo.TenantId](/api/tracon.runstartinfo/#Tracon_RunStartInfo_TenantId)) — not the *ambient* tenant.
It can deliberately override the ambient tenant (this is how workflows and
job queues work). A writer whose run was never started stays
[null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) and no tenant check is performed.

## Methods

### <a id="Tracon_RunEventWriter_AppendAsync_Tracon_RunEventDraft_System_Threading_CancellationToken_"></a> AppendAsync\(RunEventDraft, CancellationToken\)

Appends an event to the stream and assigns its sequence number.

```csharp
public ValueTask<RunEvent> AppendAsync(RunEventDraft draft, CancellationToken cancellationToken = default)
```

#### Parameters

`draft` [RunEventDraft](/api/tracon.runeventdraft/)

The event draft.

`cancellationToken` [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken)

The cancellation token.

#### Returns

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<[RunEvent](/api/tracon.runevent/)\>

The event with its sequence number and timestamp assigned. The event
is **produced** even if the store fails or the writer is
disabled.

#### Remarks

Returning the event is for workflow execution: the streaming endpoint
must both write the same event to the store and send it to the client,
and building it a second time would split the sequence number in two.
Returning a value from a disabled writer is also deliberate — closing
observability must not interrupt the response streaming to the client.
