# IRetentionStore

> 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.iretentionstore/
> Summary: IRetentionStore: The data plane that counts, batch-deletes, and reads-for-archive over the actual retention data.

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

The data plane that counts, batch-deletes, and reads-for-archive over the
actual retention data.

```csharp
public interface IRetentionStore
```

## Remarks

The target name parameter always comes from the [RetentionTargets](/api/tracon.retentiontargets/)
allowlist; implementations must throw [ArgumentException](https://learn.microsoft.com/dotnet/api/system.argumentexception) for
an unknown target.

Which column counts as "age" and which extra condition (for example, only
completed jobs) applies is fixed for EACH target and stays embedded inside
the implementation; the caller knows only the target name and the cutoff date.

**Tenant scoping is mandatory.** A retention
policy is defined per tenant; if `tenantId` is not given, the
operation touches *every* tenant's rows. Only the installation-wide
(`'*'`) policy should pass [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null).

The default (in-memory) setup registers `NullRetentionStore`, which
always returns empty/zero: retention is meaningful only when a SQL
provider is enabled.

**DI lifetime — singleton.** Registered as a singleton with
`TryAdd`; a consumer's own registration wins.

## Methods

### <a id="Tracon_IRetentionStore_ArchiveBatchAsync_System_String_System_String_System_DateTimeOffset_System_Int32_System_Func_System_Collections_Generic_IReadOnlyList_Tracon_ArchiveRow__System_Threading_CancellationToken_System_Threading_Tasks_ValueTask__System_Threading_CancellationToken_"></a> ArchiveBatchAsync\(string, string?, DateTimeOffset, int, Func<IReadOnlyList<ArchiveRow\>, CancellationToken, ValueTask\>, CancellationToken\)

Reads one batch of rows older than the cutoff date, hands it to
`archiveAsync`, and then deletes exactly the rows of that batch.

```csharp
ValueTask<int> ArchiveBatchAsync(string target, string? tenantId, DateTimeOffset cutoff, int batchSize, Func<IReadOnlyList<ArchiveRow>, CancellationToken, ValueTask> archiveAsync, CancellationToken cancellationToken = default)
```

#### Parameters

`target` [string](https://learn.microsoft.com/dotnet/api/system.string)

The target name.

`tenantId` [string](https://learn.microsoft.com/dotnet/api/system.string)?

Only this tenant's rows; if [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null), runs installation-wide
(the `'*'` policy).

`cutoff` [DateTimeOffset](https://learn.microsoft.com/dotnet/api/system.datetimeoffset)

The cutoff date (UTC).

`batchSize` [int](https://learn.microsoft.com/dotnet/api/system.int32)

The maximum number of rows to archive and delete.

`archiveAsync` [Func](https://learn.microsoft.com/dotnet/api/system.func\-3)<[IReadOnlyList](https://learn.microsoft.com/dotnet/api/system.collections.generic.ireadonlylist\-1)<[ArchiveRow](/api/tracon.archiverow/)\>, [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken), [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask)\>

Writes the batch to the archive. It is not called when no row matches.
When it throws, no row is deleted and the exception propagates.

`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)<[int](https://learn.microsoft.com/dotnet/api/system.int32)\>

The number of rows deleted. It is never more than the number of rows
handed to `archiveAsync`; zero when no row matched.

#### Remarks

This is the archive mode of a retention run: a row that was not handed to
`archiveAsync` is never deleted. A row that stops matching
between the read and the delete is kept. It is archived again when it
matches on a later run, so an [IArchiveSink](/api/tracon.iarchivesink/) can receive the
same row more than once.

For [RetentionTargets.Attachments](/api/tracon.retentiontargets/#Tracon_RetentionTargets_Attachments) with an
[IAttachmentStorage](/api/tracon.iattachmentstorage/) registered, the external content of each
archived row is deleted BEFORE the row, the same as in
[IRetentionStore.DeleteBatchAsync](/api/tracon.iretentionstore/#Tracon_IRetentionStore_DeleteBatchAsync_System_String_System_String_System_DateTimeOffset_System_Int32_System_Threading_CancellationToken_); a row whose content delete fails is kept.

The default implementation calls [IRetentionStore.ReadForArchiveAsync](/api/tracon.iretentionstore/#Tracon_IRetentionStore_ReadForArchiveAsync_System_String_System_String_System_DateTimeOffset_System_Int32_System_Threading_CancellationToken_) and then
[IRetentionStore.DeleteBatchAsync](/api/tracon.iretentionstore/#Tracon_IRetentionStore_DeleteBatchAsync_System_String_System_String_System_DateTimeOffset_System_Int32_System_Threading_CancellationToken_) with the number of rows read. It is not EXACT:
the two calls can select different rows, and a row that was never archived
can be deleted. It exists only so a store written before this member keeps
compiling. The SQL stores override it; the shipped
`RetentionStoreContract` checks the rule above.

### <a id="Tracon_IRetentionStore_CountOlderThanAsync_System_String_System_String_System_DateTimeOffset_System_Threading_CancellationToken_"></a> CountOlderThanAsync\(string, string?, DateTimeOffset, CancellationToken\)

Returns the number of rows in the target currently older than the cutoff date.

```csharp
ValueTask<long> CountOlderThanAsync(string target, string? tenantId, DateTimeOffset cutoff, CancellationToken cancellationToken = default)
```

#### Parameters

`target` [string](https://learn.microsoft.com/dotnet/api/system.string)

The target name.

`tenantId` [string](https://learn.microsoft.com/dotnet/api/system.string)?

Only this tenant's rows; if [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null), runs installation-wide
(the `'*'` policy).

`cutoff` [DateTimeOffset](https://learn.microsoft.com/dotnet/api/system.datetimeoffset)

The cutoff date (UTC). Rows older than this match.

`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 number of matching rows.

### <a id="Tracon_IRetentionStore_DeleteBatchAsync_System_String_System_String_System_DateTimeOffset_System_Int32_System_Threading_CancellationToken_"></a> DeleteBatchAsync\(string, string?, DateTimeOffset, int, CancellationToken\)

Deletes a batch of rows older than the cutoff date. This is not a
single bulk `DELETE`; it runs batch by batch, depending on the provider.

```csharp
ValueTask<int> DeleteBatchAsync(string target, string? tenantId, DateTimeOffset cutoff, int batchSize, CancellationToken cancellationToken = default)
```

#### Parameters

`target` [string](https://learn.microsoft.com/dotnet/api/system.string)

The target name.

`tenantId` [string](https://learn.microsoft.com/dotnet/api/system.string)?

Only this tenant's rows; if [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null), runs installation-wide
(the `'*'` policy).

`cutoff` [DateTimeOffset](https://learn.microsoft.com/dotnet/api/system.datetimeoffset)

The cutoff date (UTC).

`batchSize` [int](https://learn.microsoft.com/dotnet/api/system.int32)

The maximum number of rows to delete.

`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)<[int](https://learn.microsoft.com/dotnet/api/system.int32)\>

The number of rows deleted. Zero means no matching row could be deleted:
usually none remain, but see the remarks.

#### Remarks

For [RetentionTargets.Attachments](/api/tracon.retentiontargets/#Tracon_RetentionTargets_Attachments) with an
[IAttachmentStorage](/api/tracon.iattachmentstorage/) registered, each row's external content is
deleted BEFORE the row. A row whose content delete fails is kept and still
matches, so a batch can return fewer rows than
`batchSize` while matching rows remain. The next run
tries the kept row again.

The batch has no order, so this method is for a run that does not
archive. A run that archives uses [IRetentionStore.ArchiveBatchAsync](/api/tracon.iretentionstore/#Tracon_IRetentionStore_ArchiveBatchAsync_System_String_System_String_System_DateTimeOffset_System_Int32_System_Func_System_Collections_Generic_IReadOnlyList_Tracon_ArchiveRow__System_Threading_CancellationToken_System_Threading_Tasks_ValueTask__System_Threading_CancellationToken_).

### <a id="Tracon_IRetentionStore_FindRowLimitCutoffAsync_System_String_System_String_System_Int64_System_Threading_CancellationToken_"></a> FindRowLimitCutoffAsync\(string, string?, long, CancellationToken\)

Counting from newest, returns the ordering column's value at row
`maxRows`
as the cutoff date.

```csharp
ValueTask<DateTimeOffset?> FindRowLimitCutoffAsync(string target, string? tenantId, long maxRows, CancellationToken cancellationToken = default)
```

#### Parameters

`target` [string](https://learn.microsoft.com/dotnet/api/system.string)

The target name.

`tenantId` [string](https://learn.microsoft.com/dotnet/api/system.string)?

Only this tenant's rows; if [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null), runs installation-wide (the
`'*'` policy).

`maxRows` [long](https://learn.microsoft.com/dotnet/api/system.int64)

The maximum number of rows to keep (at least 1).

`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)<[DateTimeOffset](https://learn.microsoft.com/dotnet/api/system.datetimeoffset)?\>

The cutoff date; [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) if the target holds FEWER than
`maxRows`
rows (the volume limit is not exceeded).

#### Remarks

The returned value can be passed directly into this interface's other three
methods (the `@cutoff` parameter): volume-based trimming uses the SAME
batch mechanism as age-based deletion.

### <a id="Tracon_IRetentionStore_ReadForArchiveAsync_System_String_System_String_System_DateTimeOffset_System_Int32_System_Threading_CancellationToken_"></a> ReadForArchiveAsync\(string, string?, DateTimeOffset, int, CancellationToken\)

Reads a batch of rows older than the cutoff date (does not delete).

```csharp
ValueTask<IReadOnlyList<ArchiveRow>> ReadForArchiveAsync(string target, string? tenantId, DateTimeOffset cutoff, int batchSize, CancellationToken cancellationToken = default)
```

#### Parameters

`target` [string](https://learn.microsoft.com/dotnet/api/system.string)

The target name.

`tenantId` [string](https://learn.microsoft.com/dotnet/api/system.string)?

Only this tenant's rows; if [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null), runs installation-wide
(the `'*'` policy).

`cutoff` [DateTimeOffset](https://learn.microsoft.com/dotnet/api/system.datetimeoffset)

The cutoff date (UTC).

`batchSize` [int](https://learn.microsoft.com/dotnet/api/system.int32)

The maximum number of rows to read.

`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)<[ArchiveRow](/api/tracon.archiverow/)\>\>

The rows read; an empty list once no matching rows remain.

#### Remarks

The rows come in the target's time order. [IRetentionStore.DeleteBatchAsync](/api/tracon.iretentionstore/#Tracon_IRetentionStore_DeleteBatchAsync_System_String_System_String_System_DateTimeOffset_System_Int32_System_Threading_CancellationToken_)
deletes in no order, so the two calls can select different rows: archive
and delete a batch with [IRetentionStore.ArchiveBatchAsync](/api/tracon.iretentionstore/#Tracon_IRetentionStore_ArchiveBatchAsync_System_String_System_String_System_DateTimeOffset_System_Int32_System_Func_System_Collections_Generic_IReadOnlyList_Tracon_ArchiveRow__System_Threading_CancellationToken_System_Threading_Tasks_ValueTask__System_Threading_CancellationToken_) instead.
