# IRunEventSink

> 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.iruneventsink/
> Summary: IRunEventSink: Receives run events as they are written, in addition to IRunStore.

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

Receives run events as they are written, in addition to [IRunStore](/api/tracon.irunstore/).

```csharp
public interface IRunEventSink
```

## Remarks

**Runs on the hot path.** [IRunEventSink.OnEventAsync](/api/tracon.iruneventsink/#Tracon_IRunEventSink_OnEventAsync_Tracon_RunEvent_System_Threading_CancellationToken_) is awaited
before the run's response continues streaming to its own caller — queue the
event and return, do not block on further I/O.

**A sink failure never fails the run.** The writer that dispatches
to this sink logs the exception, disables the sink for the rest of the run and
keeps going — the store write and every other registered sink are unaffected.

**One instance serves every concurrent run.** A sink is resolved
once (typically as a singleton) and its [IRunEventSink.OnEventAsync](/api/tracon.iruneventsink/#Tracon_IRunEventSink_OnEventAsync_Tracon_RunEvent_System_Threading_CancellationToken_) is called
from as many runs as are in flight at once; implementations must be thread-safe.

Events from every tenant pass through the same sink instance; use
`RunEvent.TenantId` to tell them apart.

**Tenant behavior — EXPECTED tenant.** The tenant is carried
by [RunEvent.TenantId](/api/tracon.runevent/#Tracon_RunEvent_TenantId) on the event itself, the same way
[IRunStore.AppendEventAsync](/api/tracon.irunstore/#Tracon_IRunStore_AppendEventAsync_Tracon_RunEvent_System_Threading_CancellationToken_) reads it — never from the
ambient tenant, since the writing thread may not belong to the event's
own tenant.

**One event at a time per run.** A run writes from several places
at once (concurrent tool calls, a child run, a workflow's steps); Tracon numbers
an event, stores it and hands it to every sink as one step, and the run's next
event waits for it. A slow sink therefore holds up that run, which is one more
reason to queue and return. A sink must not append to the run whose event it is
handling — directly through `TraconRunContext.Current` or by starting work
inline that writes into that run, such as running an agent: the append would
wait for the event the sink is still handling, so Tracon throws
[InvalidOperationException](https://learn.microsoft.com/dotnet/api/system.invalidoperationexception) instead and the sink is disabled for the
run.

## Methods

### <a id="Tracon_IRunEventSink_OnEventAsync_Tracon_RunEvent_System_Threading_CancellationToken_"></a> OnEventAsync\(RunEvent, CancellationToken\)

Called for every event a run writes, in sequence order.

```csharp
ValueTask OnEventAsync(RunEvent runEvent, CancellationToken cancellationToken = default)
```

#### Parameters

`runEvent` [RunEvent](/api/tracon.runevent/)

The event, with its sequence number and timestamp already assigned.

`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)

The completion task.
