# ISessionStore

> 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.isessionstore/
> Summary: ISessionStore: The store for serialized agent sessions.

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

The store for serialized agent sessions.

```csharp
public interface ISessionStore
```

## Remarks

Session state is the serialized form of Microsoft Agent Framework's
`AgentSession` object and is treated as **opaque**. Its
content is not interpreted; it is only stored and restored.

**DI lifetime — singleton.** Registered as a singleton with
`TryAdd`; a consumer's own registration wins. An implementation must
be safe under concurrent calls and must not capture or depend on a
scoped service. [ISessionStore.TryCreateAsync](/api/tracon.isessionstore/#Tracon_ISessionStore_TryCreateAsync_Tracon_SessionRecord_System_Threading_CancellationToken_) and
[ISessionStore.TryUpdateAsync](/api/tracon.isessionstore/#Tracon_ISessionStore_TryUpdateAsync_Tracon_SessionRecord_System_Int64_System_Threading_CancellationToken_) specifically must be genuinely atomic, not
check-then-act — see those members' own remarks. Between them they cover
the whole lifetime of a session: the first write and every later one.

## Methods

### <a id="Tracon_ISessionStore_DeleteAsync_System_String_System_Threading_CancellationToken_"></a> DeleteAsync\(string, CancellationToken\)

Deletes the session.

```csharp
ValueTask<bool> DeleteAsync(string sessionId, CancellationToken cancellationToken = default)
```

#### Parameters

`sessionId` [string](https://learn.microsoft.com/dotnet/api/system.string)

The session 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 delete happened.

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

Fetches the session.

```csharp
ValueTask<SessionRecord?> GetAsync(string sessionId, CancellationToken cancellationToken = default)
```

#### Parameters

`sessionId` [string](https://learn.microsoft.com/dotnet/api/system.string)

The session 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)<[SessionRecord](/api/tracon.sessionrecord/)?\>

The session; [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) if it does not exist.

### <a id="Tracon_ISessionStore_GetOwnerTenantIdAsync_System_String_System_Threading_CancellationToken_"></a> GetOwnerTenantIdAsync\(string, CancellationToken\)

Returns the tenant that owns a session identifier, WITHOUT applying
the ambient tenant filter read from [ITenantContext](/api/tracon.itenantcontext/).

```csharp
ValueTask<string?> GetOwnerTenantIdAsync(string sessionId, CancellationToken cancellationToken = default)
```

#### Parameters

`sessionId` [string](https://learn.microsoft.com/dotnet/api/system.string)

The session 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)<[string](https://learn.microsoft.com/dotnet/api/system.string)?\>

The identifier of the owning tenant if the identifier has been used;
[null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) if the identifier has never been used.

#### Remarks

[ISessionStore.GetAsync](/api/tracon.isessionstore/#Tracon_ISessionStore_GetAsync_System_String_System_Threading_CancellationToken_) is filtered by the ambient tenant; because of
this, it can never answer "does this identifier belong to ANOTHER
tenant" — the caller is already inside their own tenant's context, and
another tenant's record is NEVER VISIBLE from that context, so the
result is always [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null). This is exactly
why the OpenAI-compatible endpoints' cross-tenant ownership check was
dead code — the rejection branch never fired, the identifier was
silently treated as "never used" and a new session was opened.

The default implementation calls [ISessionStore.GetAsync](/api/tracon.isessionstore/#Tracon_ISessionStore_GetAsync_System_String_System_Threading_CancellationToken_) — so it
CARRIES THE BUG ABOVE and can never correctly answer the cross-tenant
question. This exists only so old/custom stores that have not yet
overridden this method keep compiling. The real stores
(`SqlSessionStore`, `InMemorySessionStore`) override this
method with a genuinely correct implementation that is INDEPENDENT of the tenant.

### <a id="Tracon_ISessionStore_QueryAsync_Tracon_SessionQuery_System_Threading_CancellationToken_"></a> QueryAsync\(SessionQuery, CancellationToken\)

Lists sessions by filter. The most recently updated is returned first.

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

`query` [SessionQuery](/api/tracon.sessionquery/)

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)<[SessionRecord](/api/tracon.sessionrecord/)\>\>

The sessions.

### <a id="Tracon_ISessionStore_SaveAsync_Tracon_SessionRecord_System_Threading_CancellationToken_"></a> SaveAsync\(SessionRecord, CancellationToken\)

Saves the session. Overwrites an existing record with the same identifier.

```csharp
ValueTask SaveAsync(SessionRecord record, CancellationToken cancellationToken = default)
```

#### Parameters

`record` [SessionRecord](/api/tracon.sessionrecord/)

The session to save.

`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_ISessionStore_TryCreateAsync_Tracon_SessionRecord_System_Threading_CancellationToken_"></a> TryCreateAsync\(SessionRecord, CancellationToken\)

Creates a new session record only if it does not already exist.

```csharp
ValueTask<bool> TryCreateAsync(SessionRecord record, CancellationToken cancellationToken = default)
```

#### Parameters

`record` [SessionRecord](/api/tracon.sessionrecord/)

The session to create.

`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 created; [false](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/bool) if a record with the same identifier already exists.

#### Remarks

When two concurrent calls arrive with the same
[SessionRecord.Id](/api/tracon.sessionrecord/#Tracon_SessionRecord_Id), ONLY one must return
[true](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/bool); the loser must get [false](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/bool) and
read the winner's record with [ISessionStore.GetAsync](/api/tracon.isessionstore/#Tracon_ISessionStore_GetAsync_System_String_System_Threading_CancellationToken_).

The default implementation is not ATOMIC (check-then-create) — it
exists only so old stores that have not yet overridden this method
keep compiling. The real stores (`SqlSessionStore`,
`InMemorySessionStore`) override this method with a genuinely
atomic implementation. Without atomicity, two concurrent
first requests to the same new session, unaware of each other,
generate two different conversation identifiers; the second
[ISessionStore.SaveAsync](/api/tracon.isessionstore/#Tracon_ISessionStore_SaveAsync_Tracon_SessionRecord_System_Threading_CancellationToken_) unconditionally overwrites the first, and the
loser's messages become silently unreachable.

### <a id="Tracon_ISessionStore_TryUpdateAsync_Tracon_SessionRecord_System_Int64_System_Threading_CancellationToken_"></a> TryUpdateAsync\(SessionRecord, long, CancellationToken\)

Replaces an EXISTING session record, but only if it has not changed
since it was read.

```csharp
ValueTask<bool> TryUpdateAsync(SessionRecord record, long expectedVersion, CancellationToken cancellationToken = default)
```

#### Parameters

`record` [SessionRecord](/api/tracon.sessionrecord/)

The new state of the session.

`expectedVersion` [long](https://learn.microsoft.com/dotnet/api/system.int64)

The [SessionRecord.Version](/api/tracon.sessionrecord/#Tracon_SessionRecord_Version) the caller read. A record read
through [ISessionStore.GetAsync](/api/tracon.isessionstore/#Tracon_ISessionStore_GetAsync_System_String_System_Threading_CancellationToken_) or [ISessionStore.QueryAsync](/api/tracon.isessionstore/#Tracon_ISessionStore_QueryAsync_Tracon_SessionQuery_System_Threading_CancellationToken_) carries 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\-1)<[bool](https://learn.microsoft.com/dotnet/api/system.boolean)\>

[true](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/bool) if the record was replaced;
[false](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/bool) if it no longer exists or another writer
changed it first.

#### Remarks

The sibling of [ISessionStore.TryCreateAsync](/api/tracon.isessionstore/#Tracon_ISessionStore_TryCreateAsync_Tracon_SessionRecord_System_Threading_CancellationToken_), and the reason both
exist: [ISessionStore.TryCreateAsync](/api/tracon.isessionstore/#Tracon_ISessionStore_TryCreateAsync_Tracon_SessionRecord_System_Threading_CancellationToken_) makes the first write of a session
safe against a concurrent first write; this method makes every LATER
write safe against a concurrent later write. [ISessionStore.SaveAsync](/api/tracon.isessionstore/#Tracon_ISessionStore_SaveAsync_Tracon_SessionRecord_System_Threading_CancellationToken_)
overwrites unconditionally and cannot answer either question.

The write succeeds only when the stored record's
[SessionRecord.Version](/api/tracon.sessionrecord/#Tracon_SessionRecord_Version) still equals
`expectedVersion` — the value the caller read. On
success the stored version is incremented; the caller's own
`record` is not mutated.

The default implementation is not ATOMIC (read-then-write). It exists
only so a store written before this member keep compiling; the real
stores (`SqlSessionStore`, `InMemorySessionStore`) override
it with a genuinely atomic compare-and-swap. Without atomicity two
concurrent turns on the SAME EXISTING session both report success and
the loser's turn is silently overwritten — the defect this member was
added to close.
