# IWorkflowRequestStore

> 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.iworkflowrequeststore/
> Summary: IWorkflowRequestStore: The inbox of a tenant's open workflow requests: one row per request a run is waiting on, with the time the request expires and whether somebody answered it.

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

The inbox of a tenant's open workflow requests: one row per request a run is waiting on,
with the time the request expires and whether somebody answered it.

```csharp
public interface IWorkflowRequestStore
```

## Remarks

**This is a projection, not the request.** The request itself (its prompt,
its form) stays in the run's [RunEventType.WorkflowRequest](/api/tracon.runeventtype/) event; a row names
that event by its sequence number and carries only what a listing and the expiry need to
filter on. A row is written once, when the run closes as [RunStatus.AwaitingInput](/api/tracon.runstatus/),
and a row that cannot be written does not stop the run: the request is still listed by
`GET /api/workflows/runs/{runId}/requests` and can still be answered, but it is missing
from the tenant-wide list and never expires.

**Tenant behavior.** [IWorkflowRequestStore.ListAsync](/api/tracon.iworkflowrequeststore/#Tracon_IWorkflowRequestStore_ListAsync_Tracon_WorkflowRequestQuery_System_Threading_CancellationToken_), [IWorkflowRequestStore.ListByRunAsync](/api/tracon.iworkflowrequeststore/#Tracon_IWorkflowRequestStore_ListByRunAsync_System_Guid_System_Threading_CancellationToken_) and
[IWorkflowRequestStore.MarkAnsweredAsync](/api/tracon.iworkflowrequeststore/#Tracon_IWorkflowRequestStore_MarkAnsweredAsync_System_String_System_String_System_Guid_System_DateTimeOffset_System_Threading_CancellationToken_) see only the caller's tenant (`ITenantContext`); a write
uses the tenant the record carries. [IWorkflowRequestStore.ExpireAsync](/api/tracon.iworkflowrequeststore/#Tracon_IWorkflowRequestStore_ExpireAsync_System_DateTimeOffset_System_Int32_System_Threading_CancellationToken_) is maintenance work and scans
every tenant, the same reason as `IPendingApprovalStore.ExpireAsync`.

**DI lifetime.** Registered as a singleton with `TryAdd`; a consumer's own
registration wins, and each SQL provider replaces the in-memory default. An implementation
must be safe under concurrent calls: an answer and the expiry of the same request arrive
together by design.

**Delivery.** No delivery guarantee applies: every call completes or throws before it
returns, and nothing is sent later. The engine's write of a run's rows and the endpoint's
[IWorkflowRequestStore.MarkAnsweredAsync](/api/tracon.iworkflowrequeststore/#Tracon_IWorkflowRequestStore_MarkAnsweredAsync_System_String_System_String_System_Guid_System_DateTimeOffset_System_Threading_CancellationToken_) call are best-effort: a failure is logged and the run, or the
answer, goes on. [IWorkflowRequestStore.ExpireAsync](/api/tracon.iworkflowrequeststore/#Tracon_IWorkflowRequestStore_ExpireAsync_System_DateTimeOffset_System_Int32_System_Threading_CancellationToken_) hands each closed row to exactly one caller.

A SQL store ties a row to its run with a cascading foreign key, so a data subject erasure that
removes the run removes its rows. A row of a run that was closed is deleted by the
[RetentionTargets.WorkflowRequests](/api/tracon.retentiontargets/#Tracon_RetentionTargets_WorkflowRequests) retention target.

## Methods

### <a id="Tracon_IWorkflowRequestStore_AddAsync_System_Collections_Generic_IReadOnlyList_Tracon_WorkflowRequestRecord__System_Threading_CancellationToken_"></a> AddAsync\(IReadOnlyList<WorkflowRequestRecord\>, CancellationToken\)

Writes the rows of the requests a run is waiting on.

```csharp
ValueTask AddAsync(IReadOnlyList<WorkflowRequestRecord> records, CancellationToken cancellationToken = default)
```

#### Parameters

`records` [IReadOnlyList](https://learn.microsoft.com/dotnet/api/system.collections.generic.ireadonlylist\-1)<[WorkflowRequestRecord](/api/tracon.workflowrequestrecord/)\>

The rows. Every [WorkflowRequestRecord.TenantId](/api/tracon.workflowrequestrecord/#Tracon_WorkflowRequestRecord_TenantId) must be set and every
[WorkflowRequestRecord.Status](/api/tracon.workflowrequestrecord/#Tracon_WorkflowRequestRecord_Status) must be [WorkflowRequestStatus.Open](/api/tracon.workflowrequeststatus/).

`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.

#### Remarks

A row whose run and request id are already stored is left as it is, so writing the same
run twice is harmless. The run must exist: a SQL store refuses a row for a run it does not
hold.

### <a id="Tracon_IWorkflowRequestStore_ExpireAsync_System_DateTimeOffset_System_Int32_System_Threading_CancellationToken_"></a> ExpireAsync\(DateTimeOffset, int, CancellationToken\)

Closes the open rows whose [WorkflowRequestRecord.ExpiresAt](/api/tracon.workflowrequestrecord/#Tracon_WorkflowRequestRecord_ExpiresAt) has passed and
returns them.

```csharp
ValueTask<IReadOnlyList<WorkflowRequestRecord>> ExpireAsync(DateTimeOffset now, int max, CancellationToken cancellationToken = default)
```

#### Parameters

`now` [DateTimeOffset](https://learn.microsoft.com/dotnet/api/system.datetimeoffset)

Rows that expire at or before this time are closed (UTC).

`max` [int](https://learn.microsoft.com/dotnet/api/system.int32)

The upper number of rows closed 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)<[WorkflowRequestRecord](/api/tracon.workflowrequestrecord/)\>\>

The rows that were closed ([WorkflowRequestRecord.Status](/api/tracon.workflowrequestrecord/#Tracon_WorkflowRequestRecord_Status) is
[WorkflowRequestStatus.Closed](/api/tracon.workflowrequeststatus/)); [WorkflowRequestRecord.AnsweredAt](/api/tracon.workflowrequestrecord/#Tracon_WorkflowRequestRecord_AnsweredAt) tells
whether the request was answered. The order of the rows is not defined.

#### Remarks

`[TenantAgnostic]`: this is maintenance work and scans the expired rows of every tenant;
filtering by the ambient tenant would leave the requests of the other tenants open forever.
Two callers that close the same row are decided by the store: one receives the row.

### <a id="Tracon_IWorkflowRequestStore_ListAsync_Tracon_WorkflowRequestQuery_System_Threading_CancellationToken_"></a> ListAsync\(WorkflowRequestQuery, CancellationToken\)

Lists the caller's open requests, one per logical request, oldest first.

```csharp
ValueTask<IReadOnlyList<WorkflowRequestRecord>> ListAsync(WorkflowRequestQuery query, CancellationToken cancellationToken = default)
```

#### Parameters

`query` [WorkflowRequestQuery](/api/tracon.workflowrequestquery/)

The filter and the page.

`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)<[WorkflowRequestRecord](/api/tracon.workflowrequestrecord/)\>\>

The page of rows.

#### Remarks

A request is logical: the same request id in the same execution session is one request even
when several runs of the session wait on it (a run that is answered stays
[RunStatus.AwaitingInput](/api/tracon.runstatus/), and the continuation republishes the requests that
are still unanswered). The listing returns the newest open row of each, so the row names the
run to answer.

The state filter and the page are applied after that choice, in the store, so a page is never
shorter than [WorkflowRequestQuery.Take](/api/tracon.workflowrequestquery/#Tracon_WorkflowRequestQuery_Take) unless the listing ends.

The order is [WorkflowRequestRecord.RequestedAt](/api/tracon.workflowrequestrecord/#Tracon_WorkflowRequestRecord_RequestedAt), then run id, then request id; it
is stable, so [WorkflowRequestQuery.Skip](/api/tracon.workflowrequestquery/#Tracon_WorkflowRequestQuery_Skip) pages without gaps or repeats while
nothing changes.

### <a id="Tracon_IWorkflowRequestStore_ListByRunAsync_System_Guid_System_Threading_CancellationToken_"></a> ListByRunAsync\(Guid, CancellationToken\)

Lists every row of one run, open or closed, in request order.

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

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

`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)<[WorkflowRequestRecord](/api/tracon.workflowrequestrecord/)\>\>

The rows of the caller's tenant; empty when the run has none.

### <a id="Tracon_IWorkflowRequestStore_ListStrandedAsync_System_Int32_System_Threading_CancellationToken_"></a> ListStrandedAsync\(int, CancellationToken\)

Lists the closed rows of runs that still wait and have no open row left: the trace of a
process that stopped after [IWorkflowRequestStore.ExpireAsync](/api/tracon.iworkflowrequeststore/#Tracon_IWorkflowRequestStore_ExpireAsync_System_DateTimeOffset_System_Int32_System_Threading_CancellationToken_) closed the last row of a run and before
the run itself was closed.

```csharp
ValueTask<IReadOnlyList<WorkflowRequestRecord>> ListStrandedAsync(int max, CancellationToken cancellationToken = default)
```

#### Parameters

`max` [int](https://learn.microsoft.com/dotnet/api/system.int32)

The upper number of rows returned.

`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)<[WorkflowRequestRecord](/api/tracon.workflowrequestrecord/)\>\>

The rows ([WorkflowRequestRecord.Status](/api/tracon.workflowrequestrecord/#Tracon_WorkflowRequestRecord_Status) is [WorkflowRequestStatus.Closed](/api/tracon.workflowrequeststatus/)),
oldest closure first; empty when no run is stranded.

#### Remarks

`[TenantAgnostic]`: the same maintenance work as [IWorkflowRequestStore.ExpireAsync](/api/tracon.iworkflowrequeststore/#Tracon_IWorkflowRequestStore_ExpireAsync_System_DateTimeOffset_System_Int32_System_Threading_CancellationToken_), across every
tenant. A closed row is never returned by [IWorkflowRequestStore.ExpireAsync](/api/tracon.iworkflowrequeststore/#Tracon_IWorkflowRequestStore_ExpireAsync_System_DateTimeOffset_System_Int32_System_Threading_CancellationToken_) again, so without this
listing a run closed by nobody would stay [RunStatus.AwaitingInput](/api/tracon.runstatus/) and retention
would keep its request event, checkpoints and input row for good.

A store whose rows do not outlive the process (the in-memory store) cannot be left in that
state and returns an empty list.

### <a id="Tracon_IWorkflowRequestStore_MarkAnsweredAsync_System_String_System_String_System_Guid_System_DateTimeOffset_System_Threading_CancellationToken_"></a> MarkAnsweredAsync\(string, string, Guid, DateTimeOffset, CancellationToken\)

Records that a request was answered: every open row of the logical request (the same
request id in the same session, whichever run holds it) becomes answered.

```csharp
ValueTask<int> MarkAnsweredAsync(string sessionId, string requestId, Guid responseRunId, DateTimeOffset answeredAt, CancellationToken cancellationToken = default)
```

#### Parameters

`sessionId` [string](https://learn.microsoft.com/dotnet/api/system.string)

The execution session the request belongs to.

`requestId` [string](https://learn.microsoft.com/dotnet/api/system.string)

The request id.

`responseRunId` [Guid](https://learn.microsoft.com/dotnet/api/system.guid)

The run the answer started.

`answeredAt` [DateTimeOffset](https://learn.microsoft.com/dotnet/api/system.datetimeoffset)

When the answer was accepted (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\-1)<[int](https://learn.microsoft.com/dotnet/api/system.int32)\>

How many rows changed.

#### Remarks

Marking a request that is already answered replaces the run it names and keeps the first
[WorkflowRequestRecord.AnsweredAt](/api/tracon.workflowrequestrecord/#Tracon_WorkflowRequestRecord_AnsweredAt): the answer that replaces a stopped one
([WorkflowResponseStatus.Abandoned](/api/tracon.workflowresponsestatus/)) names its own run. A row that is closed is
not touched.
