# IRunStore

> 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.irunstore/
> Summary: IRunStore: Store for run records and the event stream.

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

Store for run records and the event stream.

```csharp
public interface IRunStore
```

## Remarks

Events are *append-only*. An implementation must never update or delete
an event; it only adds them through [IRunStore.AppendEventAsync](/api/tracon.irunstore/#Tracon_IRunStore_AppendEventAsync_Tracon_RunEvent_System_Threading_CancellationToken_).

**Important:** a failure in this store never interrupts the run.
Observability must not break functionality. The caller
(`RunEventWriter`) catches and logs the failure.

**Tenant behavior** is not uniform across this interface's
methods; each falls into one of three modes:

<table><thead><tr><th class="term">Mode</th><th class="description">Methods and rule</th></tr></thead><tbody><tr><td class="term">Expected tenant</td><td class="description">
[IRunStore.AppendEventAsync](/api/tracon.irunstore/#Tracon_IRunStore_AppendEventAsync_Tracon_RunEvent_System_Threading_CancellationToken_), [IRunStore.CompleteRunAsync](/api/tracon.irunstore/#Tracon_IRunStore_CompleteRunAsync_Tracon_RunCompletion_System_Threading_CancellationToken_),
[IRunStore.UpdateRunCostAsync](/api/tracon.irunstore/#Tracon_IRunStore_UpdateRunCostAsync_System_Guid_Tracon_RunCost_System_String_System_Threading_CancellationToken_),
[IRunStore.CompleteLateToolInvocationAsync](/api/tracon.irunstore/#Tracon_IRunStore_CompleteLateToolInvocationAsync_Tracon_LateToolCompletion_System_Threading_CancellationToken_). The tenant carried by the call is
compared against the record's own tenant; a mismatch fails the write (or,
for [IRunStore.UpdateRunCostAsync](/api/tracon.irunstore/#Tracon_IRunStore_UpdateRunCostAsync_System_Guid_Tracon_RunCost_System_String_System_Threading_CancellationToken_) and
[IRunStore.CompleteLateToolInvocationAsync](/api/tracon.irunstore/#Tracon_IRunStore_CompleteLateToolInvocationAsync_Tracon_LateToolCompletion_System_Threading_CancellationToken_), silently affects no row). The
ambient tenant is **not** consulted — a workflow or job
queue writes on behalf of a run whose tenant may differ from whatever is
ambient on the calling thread. A [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) tenant on the
call performs no check.
</td></tr><tr><td class="term">Ambient tenant</td><td class="description">
[IRunStore.GetRunAsync](/api/tracon.irunstore/#Tracon_IRunStore_GetRunAsync_System_Guid_System_Threading_CancellationToken_), [IRunStore.QueryRunsAsync](/api/tracon.irunstore/#Tracon_IRunStore_QueryRunsAsync_Tracon_RunQuery_System_Threading_CancellationToken_),
[IRunStore.ReadEventsAsync](/api/tracon.irunstore/#Tracon_IRunStore_ReadEventsAsync_System_Guid_System_Int64_System_Threading_CancellationToken_), [IRunStore.GetStatisticsAsync](/api/tracon.irunstore/#Tracon_IRunStore_GetStatisticsAsync_Tracon_RunStatisticsQuery_System_Threading_CancellationToken_),
[IRunStore.GetToolUsageAsync](/api/tracon.irunstore/#Tracon_IRunStore_GetToolUsageAsync_Tracon_ToolUsageQuery_System_Threading_CancellationToken_), [IRunStore.GetExperimentResultsAsync](/api/tracon.irunstore/#Tracon_IRunStore_GetExperimentResultsAsync_Tracon_ExperimentResultsQuery_System_Threading_CancellationToken_),
[IRunStore.GetTimeSeriesAsync](/api/tracon.irunstore/#Tracon_IRunStore_GetTimeSeriesAsync_Tracon_RunTimeSeriesQuery_System_Threading_CancellationToken_), [IRunStore.ListToolInvocationsAsync](/api/tracon.irunstore/#Tracon_IRunStore_ListToolInvocationsAsync_System_Guid_System_Threading_CancellationToken_),
[IRunStore.RecordToolInvocationAsync](/api/tracon.irunstore/#Tracon_IRunStore_RecordToolInvocationAsync_Tracon_ToolInvocationRecord_System_Threading_CancellationToken_). Filtered by the current
`ITenantContext` unless the query object itself carries an explicit
tenant override.
</td></tr><tr><td class="term">Tenant-independent</td><td class="description">
[IRunStore.TouchHeartbeatAsync](/api/tracon.irunstore/#Tracon_IRunStore_TouchHeartbeatAsync_System_Collections_Generic_IReadOnlyCollection_System_Guid__System_DateTimeOffset_System_Threading_CancellationToken_), [IRunStore.ClaimOrphanedRunsAsync](/api/tracon.irunstore/#Tracon_IRunStore_ClaimOrphanedRunsAsync_System_DateTimeOffset_System_Int32_System_Threading_CancellationToken_).
Maintenance work that scans every tenant's rows; see each method's own
remarks.
</td></tr></tbody></table>

**Thread safety.** An implementation is registered as a
*singleton* and must be safe under concurrent calls from
unrelated runs. It must not capture or depend on a scoped service — the
same instance answers every run's calls for the lifetime of the process.

**Null / not-found semantics** differ by method and are
documented individually; as a summary:

<table><thead><tr><th class="term">Method</th><th class="description">An id that does not exist</th></tr></thead><tbody><tr><td class="term">[IRunStore.GetRunAsync](/api/tracon.irunstore/#Tracon_IRunStore_GetRunAsync_System_Guid_System_Threading_CancellationToken_)</td><td class="description">[null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null)</td></tr><tr><td class="term">[IRunStore.ReadEventsAsync](/api/tracon.irunstore/#Tracon_IRunStore_ReadEventsAsync_System_Guid_System_Int64_System_Threading_CancellationToken_)</td><td class="description">An empty sequence — never throws</td></tr><tr><td class="term">[IRunStore.AppendEventAsync](/api/tracon.irunstore/#Tracon_IRunStore_AppendEventAsync_Tracon_RunEvent_System_Threading_CancellationToken_)</td><td class="description">Throws [TraconException](/api/tracon.traconexception/)</td></tr><tr><td class="term">[IRunStore.CompleteRunAsync](/api/tracon.irunstore/#Tracon_IRunStore_CompleteRunAsync_Tracon_RunCompletion_System_Threading_CancellationToken_)</td><td class="description">Throws [TraconException](/api/tracon.traconexception/)</td></tr></tbody></table>

## Methods

### <a id="Tracon_IRunStore_AppendEventAsync_Tracon_RunEvent_System_Threading_CancellationToken_"></a> AppendEventAsync\(RunEvent, CancellationToken\)

Appends an event to the run stream.

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

`runEvent` [RunEvent](/api/tracon.runevent/)

The event to append. The caller assigns its sequence number.

`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)

A task that completes when the event is written.

#### Remarks

**Tenant behavior — EXPECTED tenant.** When
[RunEvent.TenantId](/api/tracon.runevent/#Tracon_RunEvent_TenantId) is set, the write is filtered by it:
the event is written only if the run's own tenant matches. The
*ambient* tenant (`ITenantContext`) is deliberately
**not** consulted for this method — a workflow or a
background job queue legitimately writes on behalf of a run whose
tenant differs from whichever tenant happens to be ambient on the
calling thread. A [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) [RunEvent.TenantId](/api/tracon.runevent/#Tracon_RunEvent_TenantId)
performs no tenant check at all.

**Null / not-found semantics.** Unlike
[IRunStore.ReadEventsAsync](/api/tracon.irunstore/#Tracon_IRunStore_ReadEventsAsync_System_Guid_System_Int64_System_Threading_CancellationToken_) (which returns an empty sequence for an
unknown run), this method throws [TraconException](/api/tracon.traconexception/)
when [RunEvent.RunId](/api/tracon.runevent/#Tracon_RunEvent_RunId) does not identify an existing run,
or when it exists but does not belong to the expected tenant.

**Event order.** [RunEvent.Sequence](/api/tracon.runevent/#Tracon_RunEvent_Sequence) is
assigned by the *caller*, not by this method — the store only
writes it. The event stream is append-only: an implementation must
never update or delete a written event. A second call carrying a
[RunEvent.Sequence](/api/tracon.runevent/#Tracon_RunEvent_Sequence) value already used by this run is a
caller error, not a legitimate retry, and must be rejected with
[TraconException](/api/tracon.traconexception/) rather than silently accepted or
silently ignored — an implementation must not let a duplicate
sequence number pass through as a raw driver exception either.

### <a id="Tracon_IRunStore_ClaimOrphanedRunsAsync_System_DateTimeOffset_System_Int32_System_Threading_CancellationToken_"></a> ClaimOrphanedRunsAsync\(DateTimeOffset, int, CancellationToken\)

Closes `Running` rows, and `Queued` rows that sent a heartbeat, that have
not sent one for a long time as `Failed` and records the reason.

```csharp
ValueTask<IReadOnlyList<RunRecord>> ClaimOrphanedRunsAsync(DateTimeOffset staleBefore, int max, CancellationToken cancellationToken = default)
```

#### Parameters

`staleBefore` [DateTimeOffset](https://learn.microsoft.com/dotnet/api/system.datetimeoffset)

`Running` rows whose last heartbeat is older than this — or that never
sent one — count as orphaned, and so do `Queued` rows whose last heartbeat
is older than this (UTC).

`max` [int](https://learn.microsoft.com/dotnet/api/system.int32)

The maximum number of rows to close in this round.

`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)<[IReadOnlyList](https://learn.microsoft.com/dotnet/api/system.collections.generic.ireadonlylist\-1)<[RunRecord](/api/tracon.runrecord/)\>\>

The records of the closed runs.

#### Remarks

Closing also writes a [RunEventType.RunFailed](/api/tracon.runeventtype/) event to the
stream — the process that was writing the run is gone, so this method emits
the event itself. The sequence number is one past the current maximum.

A `Queued` row is owned by the job queue and this method DOES not TOUCH
it — unless it has a heartbeat. A process writes a heartbeat for the runs it
executes, and for a workflow run that waits in it for a concurrency slot
(`Queued` until the slot is taken), so a `Queued` row with a stale
heartbeat is a run whose process is gone. A `Queued` row that never sent
one is a run still in the queue, however old.

`[TenantAgnostic]`: this is maintenance work and scans the orphaned
rows of every tenant. Filtering by the ambient tenant would leave the rows of
other tenants `Running` forever.

### <a id="Tracon_IRunStore_CompleteLateToolInvocationAsync_Tracon_LateToolCompletion_System_Threading_CancellationToken_"></a> CompleteLateToolInvocationAsync\(LateToolCompletion, CancellationToken\)

Writes the settled outcome of a call whose timeout had already been
reported onto that call's EXISTING record.

```csharp
ValueTask<bool> CompleteLateToolInvocationAsync(LateToolCompletion completion, CancellationToken cancellationToken = default)
```

#### Parameters

`completion` [LateToolCompletion](/api/tracon.latetoolcompletion/)

The settled outcome.

`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)<[bool](https://learn.microsoft.com/dotnet/api/system.boolean)\>

[true](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/bool) when a record was updated;
[false](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/bool) when no record matches the run, the call
identifier and the tenant — the run may have been deleted by retention
before the call settled.

#### Remarks

This is the ONLY update on a tool record, and it fills in fields the
timeout left empty: the result, the reported usage and the real
duration. It never rewrites what the model was told —
[ToolInvocationRecord.TimedOut](/api/tracon.toolinvocationrecord/#Tracon_ToolInvocationRecord_TimedOut) and
[ToolInvocationRecord.Error](/api/tracon.toolinvocationrecord/#Tracon_ToolInvocationRecord_Error) stay as they were written.

A second row is deliberately not written: every per-tool total
([IRunStore.GetToolUsageAsync](/api/tracon.irunstore/#Tracon_IRunStore_GetToolUsageAsync_Tracon_ToolUsageQuery_System_Threading_CancellationToken_)) counts rows, so an extra row would
report one call as two and halve the tool's error rate.

### <a id="Tracon_IRunStore_CompleteRunAsync_Tracon_RunCompletion_System_Threading_CancellationToken_"></a> CompleteRunAsync\(RunCompletion, CancellationToken\)

Closes the run and updates its summary.

```csharp
ValueTask CompleteRunAsync(RunCompletion completion, CancellationToken cancellationToken = default)
```

#### Parameters

`completion` [RunCompletion](/api/tracon.runcompletion/)

The completion information.

`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)

A task that completes when the record is updated.

### <a id="Tracon_IRunStore_GetExperimentResultsAsync_Tracon_ExperimentResultsQuery_System_Threading_CancellationToken_"></a> GetExperimentResultsAsync\(ExperimentResultsQuery, CancellationToken\)

Summarizes an experiment per variant: count, failure rate, tokens, duration.

```csharp
ValueTask<IReadOnlyList<ExperimentVariantResult>> GetExperimentResultsAsync(ExperimentResultsQuery query, CancellationToken cancellationToken = default)
```

#### Parameters

`query` [ExperimentResultsQuery](/api/tracon.experimentresultsquery/)

The filter.

`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)<[IReadOnlyList](https://learn.microsoft.com/dotnet/api/system.collections.generic.ireadonlylist\-1)<[ExperimentVariantResult](/api/tracon.experimentvariantresult/)\>\>

Per-variant results. A variant that took no traffic is absent.

#### Remarks

The experiment breakdown is available only through this query;
`experiment_id` is not emitted as a metric tag, for cardinality reasons.

### <a id="Tracon_IRunStore_GetLastEventSequenceAsync_System_Guid_System_Threading_CancellationToken_"></a> GetLastEventSequenceAsync\(Guid, CancellationToken\)

Gets the highest [RunEvent.Sequence](/api/tracon.runevent/#Tracon_RunEvent_Sequence) written for a run.

```csharp
ValueTask<long?> GetLastEventSequenceAsync(Guid runId, CancellationToken cancellationToken = default)
```

#### Parameters

`runId` [Guid](https://learn.microsoft.com/dotnet/api/system.guid)

The run id.

`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)<[long](https://learn.microsoft.com/dotnet/api/system.int64)?\>

The highest sequence number written, or [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) when the
run has no events yet or does not exist.

#### Remarks

A run that is retried keeps its identity: the `runs` row is
upserted, not reopened. The event stream has no such upsert — it is
append-only and its sequence numbers are unique per run — so a second
attempt that restarted its numbering at zero collided with the first
attempt's events, and the writer that saw the collision disabled itself
for the rest of the run. The work then finished while the record stayed
in [RunStatus.Running](/api/tracon.runstatus/) forever: the job endpoint reported
Completed and the run endpoint contradicted it.

[RunRecord.EventCount](/api/tracon.runrecord/#Tracon_RunRecord_EventCount) cannot answer this question: it is
written at completion, and a run being taken over has not completed.

### <a id="Tracon_IRunStore_GetRunAsync_System_Guid_System_Threading_CancellationToken_"></a> GetRunAsync\(Guid, CancellationToken\)

Gets a run record.

```csharp
ValueTask<RunRecord?> GetRunAsync(Guid runId, CancellationToken cancellationToken = default)
```

#### Parameters

`runId` [Guid](https://learn.microsoft.com/dotnet/api/system.guid)

The run id.

`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)<[RunRecord](/api/tracon.runrecord/)?\>

The record, or [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) when it does not exist.

### <a id="Tracon_IRunStore_GetStatisticsAsync_Tracon_RunStatisticsQuery_System_Threading_CancellationToken_"></a> GetStatisticsAsync\(RunStatisticsQuery, CancellationToken\)

Summarizes runs.

```csharp
ValueTask<RunStatistics> GetStatisticsAsync(RunStatisticsQuery query, CancellationToken cancellationToken = default)
```

#### Parameters

`query` [RunStatisticsQuery](/api/tracon.runstatisticsquery/)

The filter.

`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)<[RunStatistics](/api/tracon.runstatistics/)\>

Counts, token totals and the per-agent breakdown.

#### Remarks

The summary is computed **inside the store**. Fetching records
and aggregating them in memory would cover only a paged subset and give the
wrong answer.

### <a id="Tracon_IRunStore_GetTimeSeriesAsync_Tracon_RunTimeSeriesQuery_System_Threading_CancellationToken_"></a> GetTimeSeriesAsync\(RunTimeSeriesQuery, CancellationToken\)

Produces the per-bucket time series of runs, failures, tokens and cost.

```csharp
ValueTask<IReadOnlyList<TimeSeriesPoint>> GetTimeSeriesAsync(RunTimeSeriesQuery query, CancellationToken cancellationToken = default)
```

#### Parameters

`query` [RunTimeSeriesQuery](/api/tracon.runtimeseriesquery/)

The filter.

`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)<[IReadOnlyList](https://learn.microsoft.com/dotnet/api/system.collections.generic.ireadonlylist\-1)<[TimeSeriesPoint](/api/tracon.timeseriespoint/)\>\>

The ordered bucket list. Empty buckets are returned too, with zero counts.

#### Exceptions

 [TraconException](/api/tracon.traconexception/)

The requested range exceeds [RunTimeSeriesBucketing.MaxBuckets](/api/tracon.runtimeseriesbucketing/#Tracon_RunTimeSeriesBucketing_MaxBuckets).

### <a id="Tracon_IRunStore_GetToolUsageAsync_Tracon_ToolUsageQuery_System_Threading_CancellationToken_"></a> GetToolUsageAsync\(ToolUsageQuery, CancellationToken\)

Summarizes usage per tool.

```csharp
ValueTask<IReadOnlyList<ToolUsage>> GetToolUsageAsync(ToolUsageQuery query, CancellationToken cancellationToken = default)
```

#### Parameters

`query` [ToolUsageQuery](/api/tracon.toolusagequery/)

The filter.

`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)<[IReadOnlyList](https://learn.microsoft.com/dotnet/api/system.collections.generic.ireadonlylist\-1)<[ToolUsage](/api/tracon.toolusage/)\>\>

Call counts, failure rates and average durations.

#### Remarks

Same reason as [IRunStore.GetStatisticsAsync](/api/tracon.irunstore/#Tracon_IRunStore_GetStatisticsAsync_Tracon_RunStatisticsQuery_System_Threading_CancellationToken_): the summary is computed
**inside the store**.

### <a id="Tracon_IRunStore_ListNonTokenUsageAsync_System_Guid_System_Threading_CancellationToken_"></a> ListNonTokenUsageAsync\(Guid, CancellationToken\)

Lists the non-token items a run's workflow function nodes reported, in the order reported.

```csharp
ValueTask<IReadOnlyList<NonTokenUsageRecord>> ListNonTokenUsageAsync(Guid runId, CancellationToken cancellationToken = default)
```

#### Parameters

`runId` [Guid](https://learn.microsoft.com/dotnet/api/system.guid)

The run id.

`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)<[IReadOnlyList](https://learn.microsoft.com/dotnet/api/system.collections.generic.ireadonlylist\-1)<[NonTokenUsageRecord](/api/tracon.nontokenusagerecord/)\>\>

The items; empty when the run reported none, or when it does not exist or belongs to
another tenant.

#### Remarks

The items of the run itself only, not of the runs below it. Tool measurements stay on
[IRunStore.ListToolInvocationsAsync](/api/tracon.irunstore/#Tracon_IRunStore_ListToolInvocationsAsync_System_Guid_System_Threading_CancellationToken_).

### <a id="Tracon_IRunStore_ListToolInvocationsAsync_System_Guid_System_Threading_CancellationToken_"></a> ListToolInvocationsAsync\(Guid, CancellationToken\)

Lists the tool calls of a run in chronological order.

```csharp
ValueTask<IReadOnlyList<ToolInvocationRecord>> ListToolInvocationsAsync(Guid runId, CancellationToken cancellationToken = default)
```

#### Parameters

`runId` [Guid](https://learn.microsoft.com/dotnet/api/system.guid)

The run id.

`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)<[IReadOnlyList](https://learn.microsoft.com/dotnet/api/system.collections.generic.ireadonlylist\-1)<[ToolInvocationRecord](/api/tracon.toolinvocationrecord/)\>\>

The call records.

### <a id="Tracon_IRunStore_QueryRunsAsync_Tracon_RunQuery_System_Threading_CancellationToken_"></a> QueryRunsAsync\(RunQuery, CancellationToken\)

Lists runs that match a filter, newest first.

```csharp
ValueTask<IReadOnlyList<RunRecord>> QueryRunsAsync(RunQuery query, CancellationToken cancellationToken = default)
```

#### Parameters

`query` [RunQuery](/api/tracon.runquery/)

The filter.

`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)<[IReadOnlyList](https://learn.microsoft.com/dotnet/api/system.collections.generic.ireadonlylist\-1)<[RunRecord](/api/tracon.runrecord/)\>\>

The matching records.

### <a id="Tracon_IRunStore_ReadEventsAsync_System_Guid_System_Int64_System_Threading_CancellationToken_"></a> ReadEventsAsync\(Guid, long, CancellationToken\)

Reads the events of a run in sequence order. Live streaming and historical
replay take the same path.

```csharp
IAsyncEnumerable<RunEvent> ReadEventsAsync(Guid runId, long fromSequence = 0, CancellationToken cancellationToken = default)
```

#### Parameters

`runId` [Guid](https://learn.microsoft.com/dotnet/api/system.guid)

The run id.

`fromSequence` [long](https://learn.microsoft.com/dotnet/api/system.int64)

Reading starts at this sequence number, inclusive.

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

#### Returns

 [IAsyncEnumerable](https://learn.microsoft.com/dotnet/api/system.collections.generic.iasyncenumerable\-1)<[RunEvent](/api/tracon.runevent/)\>

The ordered event stream.

### <a id="Tracon_IRunStore_RecordNonTokenUsageAsync_Tracon_NonTokenUsageRecord_System_Threading_CancellationToken_"></a> RecordNonTokenUsageAsync\(NonTokenUsageRecord, CancellationToken\)

Records one non-token item a workflow function node reported.

```csharp
ValueTask RecordNonTokenUsageAsync(NonTokenUsageRecord record, CancellationToken cancellationToken = default)
```

#### Parameters

`record` [NonTokenUsageRecord](/api/tracon.nontokenusagerecord/)

The reported item.

`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)

A task that completes when the item is written.

#### Remarks

Written the moment the node reports it, not at the end of the run, so a run that
fails or is cancelled keeps what it had already spent. The tenant guard is the
one of [IRunStore.RecordToolInvocationAsync](/api/tracon.irunstore/#Tracon_IRunStore_RecordToolInvocationAsync_Tracon_ToolInvocationRecord_System_Threading_CancellationToken_): when
[NonTokenUsageRecord.TenantId](/api/tracon.nontokenusagerecord/#Tracon_NonTokenUsageRecord_TenantId) is set, the item is written only if the
run's own tenant matches.

The item does not change [RunRecord.NonTokenCost](/api/tracon.runrecord/#Tracon_RunRecord_NonTokenCost); that snapshot comes
with [IRunStore.CompleteRunAsync](/api/tracon.irunstore/#Tracon_IRunStore_CompleteRunAsync_Tracon_RunCompletion_System_Threading_CancellationToken_).

### <a id="Tracon_IRunStore_RecordToolInvocationAsync_Tracon_ToolInvocationRecord_System_Threading_CancellationToken_"></a> RecordToolInvocationAsync\(ToolInvocationRecord, CancellationToken\)

Records a settled tool call.

```csharp
ValueTask RecordToolInvocationAsync(ToolInvocationRecord invocation, CancellationToken cancellationToken = default)
```

#### Parameters

`invocation` [ToolInvocationRecord](/api/tracon.toolinvocationrecord/)

The call summary.

`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)

A task that completes when the record is written.

#### Remarks

Kept apart from the event stream because durations and per-tool totals must
be queryable without scanning the whole event stream.

### <a id="Tracon_IRunStore_StartRunAsync_Tracon_RunStartInfo_System_Threading_CancellationToken_"></a> StartRunAsync\(RunStartInfo, CancellationToken\)

Opens a new run record, or updates it if one already exists.

```csharp
ValueTask<RunRecord> StartRunAsync(RunStartInfo info, CancellationToken cancellationToken = default)
```

#### Parameters

`info` [RunStartInfo](/api/tracon.runstartinfo/)

The start information.

`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)<[RunRecord](/api/tracon.runrecord/)\>

The effective record as it now stands, persisted. [RunRecord.UserId](/api/tracon.runrecord/#Tracon_RunRecord_UserId)
and [RunRecord.Labels](/api/tracon.runrecord/#Tracon_RunRecord_Labels) reflect this call's own COALESCE
against the previous row (see **Idempotency** below), not
necessarily the values this call was given.

#### Remarks

**Idempotency.** This method is an UPSERT: calling it a
second time with a [RunStartInfo.RunId](/api/tracon.runstartinfo/#Tracon_RunStartInfo_RunId) that already
exists does not open a second row, it updates the existing one. This
exists because a queued run is written twice — a placeholder row is
written first (typically [RunStatus.Queued](/api/tracon.runstatus/)), then
rewritten once a worker actually starts it (typically
[RunStatus.Running](/api/tracon.runstatus/)); a workflow run that waits in its process
for a concurrency slot is written the same way. The second call leaves the
run's heartbeat ([IRunStore.TouchHeartbeatAsync](/api/tracon.irunstore/#Tracon_IRunStore_TouchHeartbeatAsync_System_Collections_Generic_IReadOnlyCollection_System_Guid__System_DateTimeOffset_System_Threading_CancellationToken_)) as it was.

Every field is overwritten by the second call **except**
[RunStartInfo.UserId](/api/tracon.runstartinfo/#Tracon_RunStartInfo_UserId) and [RunStartInfo.Labels](/api/tracon.runstartinfo/#Tracon_RunStartInfo_Labels),
which are COALESCED: a [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) value on the second call
leaves the previous value in place, a non-null value replaces it. The
reason is attribution: the first write (typically inside the HTTP
request, where the caller's identity is known) may know the user; the
second write (typically a background worker, which has no request to
read identity from) usually does not, and must not erase what the
first write recorded.

A conforming implementation must apply this same COALESCE rule to the
value it returns — a caller that reads the return value instead of
re-querying the record must see the same attribution a
[IRunStore.GetRunAsync](/api/tracon.irunstore/#Tracon_IRunStore_GetRunAsync_System_Guid_System_Threading_CancellationToken_) call would.

### <a id="Tracon_IRunStore_TouchHeartbeatAsync_System_Collections_Generic_IReadOnlyCollection_System_Guid__System_DateTimeOffset_System_Threading_CancellationToken_"></a> TouchHeartbeatAsync\(IReadOnlyCollection<Guid\>, DateTimeOffset, CancellationToken\)

Updates the "still here" mark of in-flight runs in one batch.

```csharp
ValueTask TouchHeartbeatAsync(IReadOnlyCollection<Guid> runIds, DateTimeOffset at, CancellationToken cancellationToken = default)
```

#### Parameters

`runIds` [IReadOnlyCollection](https://learn.microsoft.com/dotnet/api/system.collections.generic.ireadonlycollection\-1)<[Guid](https://learn.microsoft.com/dotnet/api/system.guid)\>

The ids of the runs to mark.

`at` [DateTimeOffset](https://learn.microsoft.com/dotnet/api/system.datetimeoffset)

The mark time (UTC).

`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)

A task that completes when the marks are written.

#### Remarks

Only `Running` and `Queued` rows are affected (a workflow run that
waits in its process for a concurrency slot is `Queued` and still owned
by that process); an id that does not exist or is in another status is
skipped silently — this is a maintenance signal and must not interrupt a run.

`[TenantAgnostic]`: the ids come from the calling process's own
`IRunCancellationRegistry` and are therefore already limited to the runs
that process actually executes. A tenant filter would also need one query per
tenant, which defeats the point of a heartbeat: a cheap signal that stays off
the hot path.

### <a id="Tracon_IRunStore_UpdateRunCostAsync_System_Guid_Tracon_RunCost_System_String_System_Threading_CancellationToken_"></a> UpdateRunCostAsync\(Guid, RunCost?, string?, CancellationToken\)

Updates the cost of a run. Used only by the maintenance endpoint (`POST
/api/stats/recalculate-costs`); on the normal path the cost is written once
by [IRunStore.CompleteRunAsync](/api/tracon.irunstore/#Tracon_IRunStore_CompleteRunAsync_Tracon_RunCompletion_System_Threading_CancellationToken_).

```csharp
ValueTask UpdateRunCostAsync(Guid runId, RunCost? cost, string? tenantId, CancellationToken cancellationToken = default)
```

#### Parameters

`runId` [Guid](https://learn.microsoft.com/dotnet/api/system.guid)

The run id.

`cost` [RunCost](/api/tracon.runcost/)?

The new cost. May be [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null).

`tenantId` [string](https://learn.microsoft.com/dotnet/api/system.string)?

The EXPECTED tenant of the run. Defence in depth: the call has no default, so a
caller states the tenant on purpose. [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) makes no tenant
check, and only a maintenance path that already filtered by tenant passes it.

`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)

A task that completes when the record is updated.
