Skip to content
Tracon

IWorkflowResponseStore

Namespace Tracon · Assembly Tracon.Abstractions.dll

Where the answers to a workflow’s pending human requests are recorded: which run answered which request of which run, who answered, and whether the single answer a WorkflowResponseMode.SingleAnswer workflow allows is already taken.

public interface IWorkflowResponseStore

The store keeps the decision, not its content: a record carries a SHA-256 of the answer, never the answer itself. The request is still read from the waiting run’s RunEventType.WorkflowRequest event.

Tenant behavior. Reads and updates use the ambient tenant, read from ITenantContext: IWorkflowResponseStore.FindClaimAsync, IWorkflowResponseStore.ReplaceAsync (the record it abandons), IWorkflowResponseStore.MarkDeliveredAsync, IWorkflowResponseStore.ListBySourceRunAsync and IWorkflowResponseStore.GetAsync see only the caller’s tenant. A write uses the expected tenant the record carries (WorkflowResponseRecord.TenantId). Two tenants can hold a claim with the same scope and request id without seeing each other.

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: two answers to one request arrive together by design.

Delivery. No delivery guarantee applies: every call completes or throws before it returns, and nothing is sent later. The respond endpoint refuses an answer whose record cannot be written; the engine’s IWorkflowResponseStore.MarkDeliveredAsync call is best-effort and only logged when it fails.

A record is deleted only with its source run: a SQL store ties it to the run with a cascading foreign key, and a data subject erasure that removes the source run removes its records too. No retention target deletes runs, so the records of an installation grow until such an erasure; plan for that the way you plan for the run history itself.

AddAsync(WorkflowResponseRecord, CancellationToken)

Section titled “ AddAsync(WorkflowResponseRecord, CancellationToken)”

Records an answer. When the record carries a WorkflowResponseRecord.ClaimScope, it is written only if no other active record holds the same scope and request id.

ValueTask<WorkflowResponseClaim> AddAsync(WorkflowResponseRecord record, CancellationToken cancellationToken = default)

record WorkflowResponseRecord

The answer to record. WorkflowResponseRecord.Status must be WorkflowResponseStatus.Accepted and WorkflowResponseRecord.TenantId must be set.

cancellationToken CancellationToken

The cancellation token.

ValueTask<WorkflowResponseClaim>

WorkflowResponseClaim.Claimed is true when the record was written. It is false when another active record already holds the claim; WorkflowResponseClaim.Existing is that record.

Two concurrent writes with the same claim are decided by the store, atomically: exactly one of them is written. A record without a claim scope is always written.

FindClaimAsync(string, string, CancellationToken)

Section titled “ FindClaimAsync(string, string, CancellationToken)”

Finds the active record that holds a claim.

ValueTask<WorkflowResponseRecord?> FindClaimAsync(string claimScope, string requestId, CancellationToken cancellationToken = default)

claimScope string

The claim scope (the execution session of the waiting run).

requestId string

The request id.

cancellationToken CancellationToken

The cancellation token.

ValueTask<WorkflowResponseRecord?>

The record, or null when no record of the caller’s tenant that is not WorkflowResponseStatus.Abandoned holds the claim.

Returns the record of one answering run.

ValueTask<WorkflowResponseRecord?> GetAsync(Guid responseRunId, CancellationToken cancellationToken = default)

responseRunId Guid

The run that answered.

cancellationToken CancellationToken

The cancellation token.

ValueTask<WorkflowResponseRecord?>

The record, or null when it is not found in the caller’s tenant.

ListBySourceRunAsync(Guid, CancellationToken)

Section titled “ ListBySourceRunAsync(Guid, CancellationToken)”

Lists every answer given to a run’s requests, oldest first.

ValueTask<IReadOnlyList<WorkflowResponseRecord>> ListBySourceRunAsync(Guid sourceRunId, CancellationToken cancellationToken = default)

sourceRunId Guid

The run that waited for the answers.

cancellationToken CancellationToken

The cancellation token.

ValueTask<IReadOnlyList<WorkflowResponseRecord>>

The records of the caller’s tenant, ordered by WorkflowResponseRecord.RespondedAt.

MarkDeliveredAsync(Guid, CancellationToken)

Section titled “ MarkDeliveredAsync(Guid, CancellationToken)”

Records that the workflow received the answer: an WorkflowResponseStatus.Accepted record becomes WorkflowResponseStatus.Delivered.

ValueTask MarkDeliveredAsync(Guid responseRunId, CancellationToken cancellationToken = default)

responseRunId Guid

The run that delivered the answer.

cancellationToken CancellationToken

The cancellation token.

ValueTask

The completion task.

Does nothing when no record of the caller’s tenant has this run id, or when the record is no longer WorkflowResponseStatus.Accepted.

ReplaceAsync(Guid, WorkflowResponseRecord, CancellationToken)

Section titled “ ReplaceAsync(Guid, WorkflowResponseRecord, CancellationToken)”

Abandons an active record and writes the record of the run that replaces it, as one atomic step.

ValueTask<bool> ReplaceAsync(Guid abandonedResponseRunId, WorkflowResponseRecord replacement, CancellationToken cancellationToken = default)

abandonedResponseRunId Guid

The run id of the record to abandon.

replacement WorkflowResponseRecord

The record that takes over the claim. WorkflowResponseRecord.Status must be WorkflowResponseStatus.Accepted.

cancellationToken CancellationToken

The cancellation token.

ValueTask<bool>

true when the record was abandoned and replacement written; false when the record is not found in the caller’s tenant or is already WorkflowResponseStatus.Abandoned, in which case nothing is written.

Two concurrent calls that abandon the same record are decided by the store: one returns true, the other false.