# IJobStore

> 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.ijobstore/
> Summary: IJobStore: The store for queued jobs and their items.

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

The store for queued jobs and their items.

```csharp
public interface IJobStore
```

## Remarks

The PostgreSql implementation leases with `FOR UPDATE SKIP LOCKED`:
even if multiple workers connect to the same database, a job is picked up
by only one worker. The in-memory implementation provides the same
contract with a lock and a timestamp.

**Important:** [IJobStore.ReportItemAsync](/api/tracon.ijobstore/#Tracon_IJobStore_ReportItemAsync_Tracon_JobItemResult_System_Threading_CancellationToken_) and state
transitions must be idempotent — the same item may be reported twice if
the lease expires and the job is re-leased.

## Methods

### <a id="Tracon_IJobStore_CancelAsync_System_String_System_Guid_System_Threading_CancellationToken_"></a> CancelAsync\(string, Guid, CancellationToken\)

Tries to cancel a job. Only a job in the [JobStatus.Pending](/api/tracon.jobstatus/),
[JobStatus.Leased](/api/tracon.jobstatus/), or [JobStatus.Running](/api/tracon.jobstatus/)
state can be cancelled.

```csharp
ValueTask<bool> CancelAsync(string tenantId, Guid jobId, CancellationToken cancellationToken = default)
```

#### Parameters

`tenantId` [string](https://learn.microsoft.com/dotnet/api/system.string)

The tenant identifier.

`jobId` [Guid](https://learn.microsoft.com/dotnet/api/system.guid)

The job identifier.

`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) if the cancellation happened.

### <a id="Tracon_IJobStore_CompleteAsync_Tracon_JobCompletion_System_Threading_CancellationToken_"></a> CompleteAsync\(JobCompletion, CancellationToken\)

Finalizes a job.

```csharp
ValueTask CompleteAsync(JobCompletion completion, CancellationToken cancellationToken = default)
```

#### Parameters

`completion` [JobCompletion](/api/tracon.jobcompletion/)

The finalization 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)

The completion task.

#### Remarks

Only an open job ([JobStatus.Pending](/api/tracon.jobstatus/), [JobStatus.Leased](/api/tracon.jobstatus/)
or [JobStatus.Running](/api/tracon.jobstatus/)) is finalized; the call leaves a job
that already reached a terminal status unchanged. A cancelled job stays
[JobStatus.Cancelled](/api/tracon.jobstatus/) even when its handler throws afterwards.

### <a id="Tracon_IJobStore_EnqueueAsync_Tracon_JobRecord_System_Collections_Generic_IReadOnlyList_System_String__System_Threading_CancellationToken_"></a> EnqueueAsync\(JobRecord, IReadOnlyList<string\>, CancellationToken\)

Enqueues a new job and creates its items.

```csharp
ValueTask<JobRecord> EnqueueAsync(JobRecord job, IReadOnlyList<string> items, CancellationToken cancellationToken = default)
```

#### Parameters

`job` [JobRecord](/api/tracon.jobrecord/)

The job record. [JobRecord.Status](/api/tracon.jobrecord/#Tracon_JobRecord_Status) is ignored; the store
always starts it with [JobStatus.Pending](/api/tracon.jobstatus/).

`items` [IReadOnlyList](https://learn.microsoft.com/dotnet/api/system.collections.generic.ireadonlylist\-1)<[string](https://learn.microsoft.com/dotnet/api/system.string)\>

The job's input list. Sequence numbers are assigned by list order.

`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)<[JobRecord](/api/tracon.jobrecord/)\>

The created job record.

#### Remarks

Resolve the stored lane with [JobLanes.Resolve](/api/tracon.joblanes/#Tracon_JobLanes_Resolve_System_String_System_String_System_Collections_Generic_IDictionary_System_String_System_String__), passing the
configured `TraconSchedulingOptions.LaneByHandlerKey` map. That map is
applied here and nowhere else, so a store that skips the call silently
ignores it.

**A job id that is already stored is refused:** the call throws,
and the stored job and its items do not change. A queued run's job carries the
run's id, and two callers can write the same job at the same time (a repeated
answer that writes the job a crash left missing); the refusal is what keeps the
work from running twice. A store that replaced the job would reset a leased
job to [JobStatus.Pending](/api/tracon.jobstatus/).

### <a id="Tracon_IJobStore_GetAsync_System_String_System_Guid_System_Threading_CancellationToken_"></a> GetAsync\(string, Guid, CancellationToken\)

Fetches a single job record.

```csharp
ValueTask<JobRecord?> GetAsync(string tenantId, Guid jobId, CancellationToken cancellationToken = default)
```

#### Parameters

`tenantId` [string](https://learn.microsoft.com/dotnet/api/system.string)

The tenant identifier.

`jobId` [Guid](https://learn.microsoft.com/dotnet/api/system.guid)

The job identifier.

`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)<[JobRecord](/api/tracon.jobrecord/)?\>

The record; [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) if it does not exist or belongs to another tenant.

### <a id="Tracon_IJobStore_GetQueueDepthAsync_System_Threading_CancellationToken_"></a> GetQueueDepthAsync\(CancellationToken\)

Counts the jobs still outstanding, grouped by lane and status.

```csharp
ValueTask<IReadOnlyList<JobQueueDepth>> GetQueueDepthAsync(CancellationToken cancellationToken = default)
```

#### Parameters

`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)<[JobQueueDepth](/api/tracon.jobqueuedepth/)\>\>

One record per lane/status pair that has at least one open job. A pair
with no open jobs is omitted rather than reported as zero.

#### Remarks

Only the open statuses are counted — [JobStatus.Pending](/api/tracon.jobstatus/),
[JobStatus.Leased](/api/tracon.jobstatus/), and [JobStatus.Running](/api/tracon.jobstatus/). The
terminal ones are already counted by the
`tracon.job.executions` counter as each job finishes; counting
them here would make the query's cost grow with the queue's whole
history instead of with the work that is actually outstanding.

This is an aggregate query, deliberately placed on this interface rather
than split into a separate one — the same layout
[IRunStore.GetStatisticsAsync](/api/tracon.irunstore/#Tracon_IRunStore_GetStatisticsAsync_Tracon_RunStatisticsQuery_System_Threading_CancellationToken_) already uses. It takes
**no tenant argument**: queue depth is an operator signal
about the worker pool, which leases across every tenant
([IJobStore.LeaseAsync](/api/tracon.ijobstore/#Tracon_IJobStore_LeaseAsync_Tracon_JobLeaseRequest_System_Threading_CancellationToken_)), so there is no tenant boundary to apply here.

The SQL implementations answer this from the `jobs_claim_idx`
index, whose partial condition covers exactly these three statuses.

### <a id="Tracon_IJobStore_LeaseAsync_Tracon_JobLeaseRequest_System_Threading_CancellationToken_"></a> LeaseAsync\(JobLeaseRequest, CancellationToken\)

Leases the oldest job ready to run. Returns [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) if
no such job exists.

```csharp
ValueTask<JobRecord?> LeaseAsync(JobLeaseRequest request, CancellationToken cancellationToken = default)
```

#### Parameters

`request` [JobLeaseRequest](/api/tracon.jobleaserequest/)

The lease request: the worker, the lease duration, the lanes
([JobLeaseRequest.Lanes](/api/tracon.jobleaserequest/#Tracon_JobLeaseRequest_Lanes), [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) or empty applies no
filter) and an optional per-tenant ceiling ([JobLeaseRequest.TenantCap](/api/tracon.jobleaserequest/#Tracon_JobLeaseRequest_TenantCap)).

`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)<[JobRecord](/api/tracon.jobrecord/)?\>

The leased job; [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) if none exists.

#### Remarks

With [JobLeaseRequest.TenantCap](/api/tracon.jobleaserequest/#Tracon_JobLeaseRequest_TenantCap) set, a ready job whose handler key is in
the cap's keys is skipped while its tenant already holds the cap's number of live
leases on those keys; the oldest job that is still eligible is leased instead. A job
with an expired lease is not counted, and it can be leased again. With no cap the
oldest ready job is leased, whichever tenant owns it.

The count is read, not locked: workers that lease at the same moment can take the
tenant past the ceiling by up to their own number (see [JobTenantCap](/api/tracon.jobtenantcap/)).

### <a id="Tracon_IJobStore_ListItemsAsync_System_Guid_System_Threading_CancellationToken_"></a> ListItemsAsync\(Guid, CancellationToken\)

Lists a job's items, by sequence number.

```csharp
ValueTask<IReadOnlyList<JobItemRecord>> ListItemsAsync(Guid jobId, CancellationToken cancellationToken = default)
```

#### Parameters

`jobId` [Guid](https://learn.microsoft.com/dotnet/api/system.guid)

The job identifier.

`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)<[JobItemRecord](/api/tracon.jobitemrecord/)\>\>

The items.

### <a id="Tracon_IJobStore_MarkRunningAsync_System_Guid_System_String_System_Threading_CancellationToken_"></a> MarkRunningAsync\(Guid, string, CancellationToken\)

Transitions a leased ([JobStatus.Leased](/api/tracon.jobstatus/)) job to
[JobStatus.Running](/api/tracon.jobstatus/). The worker calls this right after
obtaining the lease, before starting execution.

```csharp
ValueTask<bool> MarkRunningAsync(Guid jobId, string owner, CancellationToken cancellationToken = default)
```

#### Parameters

`jobId` [Guid](https://learn.microsoft.com/dotnet/api/system.guid)

The job identifier.

`owner` [string](https://learn.microsoft.com/dotnet/api/system.string)

The identifier of the worker holding the lease. The operation is ignored if it does not 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)<[bool](https://learn.microsoft.com/dotnet/api/system.boolean)\>

[true](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/bool) if the transition happened.

### <a id="Tracon_IJobStore_QueryAsync_Tracon_JobQuery_System_Threading_CancellationToken_"></a> QueryAsync\(JobQuery, CancellationToken\)

Lists jobs by filter. The newest record is returned first.

```csharp
ValueTask<IReadOnlyList<JobRecord>> QueryAsync(JobQuery query, CancellationToken cancellationToken = default)
```

#### Parameters

`query` [JobQuery](/api/tracon.jobquery/)

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)<[JobRecord](/api/tracon.jobrecord/)\>\>

The records.

### <a id="Tracon_IJobStore_ReleaseForRetryAsync_System_Guid_System_String_System_Nullable_System_TimeSpan__System_Threading_CancellationToken_"></a> ReleaseForRetryAsync\(Guid, string, TimeSpan?, CancellationToken\)

Returns a job to the [JobStatus.Pending](/api/tracon.jobstatus/) state; releases
its lease. [JobRecord.Attempt](/api/tracon.jobrecord/#Tracon_JobRecord_Attempt) does not change here since
it is already incremented at lease time.

```csharp
ValueTask ReleaseForRetryAsync(Guid jobId, string errorMessage, TimeSpan? retryAfter = null, CancellationToken cancellationToken = default)
```

#### Parameters

`jobId` [Guid](https://learn.microsoft.com/dotnet/api/system.guid)

The job identifier.

`errorMessage` [string](https://learn.microsoft.com/dotnet/api/system.string)

The most recent attempt's error.

`retryAfter` [TimeSpan](https://learn.microsoft.com/dotnet/api/system.timespan)?

The time to wait before the next attempt. If [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) or
zero, the job may be re-leased immediately (the old behavior).

`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

`retryAfter` pushes the [JobRecord.ScheduledFor](/api/tracon.jobrecord/#Tracon_JobRecord_ScheduledFor)
field forward; since the lease query already applies
`scheduled_for <= now`, backoff needs no additional mechanism.

Webhook delivery builds its 1 min / 5 min / 30 min / 2 hr /
6 hr ladder with this parameter. No second queue or second lease
mechanism is written.

Like [IJobStore.CompleteAsync](/api/tracon.ijobstore/#Tracon_IJobStore_CompleteAsync_Tracon_JobCompletion_System_Threading_CancellationToken_), the call leaves a job in a terminal
status unchanged: a cancelled job never returns to
[JobStatus.Pending](/api/tracon.jobstatus/).

### <a id="Tracon_IJobStore_RenewLeaseAsync_System_Guid_System_String_System_TimeSpan_System_Threading_CancellationToken_"></a> RenewLeaseAsync\(Guid, string, TimeSpan, CancellationToken\)

Extends an in-progress job's lease. Does not change the job's status.

```csharp
ValueTask RenewLeaseAsync(Guid jobId, string owner, TimeSpan leaseDuration, CancellationToken cancellationToken = default)
```

#### Parameters

`jobId` [Guid](https://learn.microsoft.com/dotnet/api/system.guid)

The job identifier.

`owner` [string](https://learn.microsoft.com/dotnet/api/system.string)

The identifier of the worker holding the lease. The operation is ignored if it does not match.

`leaseDuration` [TimeSpan](https://learn.microsoft.com/dotnet/api/system.timespan)

The new lease duration.

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

### <a id="Tracon_IJobStore_ReportItemAsync_Tracon_JobItemResult_System_Threading_CancellationToken_"></a> ReportItemAsync\(JobItemResult, CancellationToken\)

Reports an item's processing result and updates the job's
[JobRecord.DoneItems](/api/tracon.jobrecord/#Tracon_JobRecord_DoneItems)/[JobRecord.FailedItems](/api/tracon.jobrecord/#Tracon_JobRecord_FailedItems) counters.

```csharp
ValueTask ReportItemAsync(JobItemResult item, CancellationToken cancellationToken = default)
```

#### Parameters

`item` [JobItemResult](/api/tracon.jobitemresult/)

The item result.

`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

Only an item that is still [JobItemStatus.Pending](/api/tracon.jobitemstatus/) is changed. A
report for an item that already finished changes neither the item nor the
counters, so an item re-reported after a lease takeover is counted once.

A report whose status is [JobItemStatus.Pending](/api/tracon.jobitemstatus/) is a marker: it
records [JobItemResult.RunId](/api/tracon.jobitemresult/#Tracon_JobItemResult_RunId) on the item, leaves the item
pending, and counts nothing. A later report that finishes the item is
counted as usual.
