.NET API
ISessionStore
Tracon.Abstractions.dllThe store for serialized agent sessions.
public interface ISessionStoreRemarks
Section titled “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 and
ISessionStore.TryUpdateAsync 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
Section titled “Methods”DeleteAsync(string, CancellationToken)
Section titled “ DeleteAsync(string, CancellationToken)”Deletes the session.
ValueTask<bool> DeleteAsync(string sessionId, CancellationToken cancellationToken = default)Parameters
Section titled “Parameters”sessionId string
The session identifier.
cancellationToken CancellationToken
The cancellation token.
Returns
Section titled “Returns”true if the delete happened.
GetAsync(string, CancellationToken)
Section titled “ GetAsync(string, CancellationToken)”Fetches the session.
ValueTask<SessionRecord?> GetAsync(string sessionId, CancellationToken cancellationToken = default)Parameters
Section titled “Parameters”sessionId string
The session identifier.
cancellationToken CancellationToken
The cancellation token.
Returns
Section titled “Returns”The session; null if it does not exist.
GetOwnerTenantIdAsync(string, CancellationToken)
Section titled “ GetOwnerTenantIdAsync(string, CancellationToken)”Returns the tenant that owns a session identifier, WITHOUT applying the ambient tenant filter read from ITenantContext.
ValueTask<string?> GetOwnerTenantIdAsync(string sessionId, CancellationToken cancellationToken = default)Parameters
Section titled “Parameters”sessionId string
The session identifier.
cancellationToken CancellationToken
The cancellation token.
Returns
Section titled “Returns”The identifier of the owning tenant if the identifier has been used; null if the identifier has never been used.
Remarks
Section titled “Remarks”ISessionStore.GetAsync 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. 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 — 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.
QueryAsync(SessionQuery, CancellationToken)
Section titled “ QueryAsync(SessionQuery, CancellationToken)”Lists sessions by filter. The most recently updated is returned first.
ValueTask<IReadOnlyList<SessionRecord>> QueryAsync(SessionQuery query, CancellationToken cancellationToken = default)Parameters
Section titled “Parameters”query SessionQuery
The filter.
cancellationToken CancellationToken
The cancellation token.
Returns
Section titled “Returns”ValueTask<IReadOnlyList<SessionRecord>>
The sessions.
SaveAsync(SessionRecord, CancellationToken)
Section titled “ SaveAsync(SessionRecord, CancellationToken)”Saves the session. Overwrites an existing record with the same identifier.
ValueTask SaveAsync(SessionRecord record, CancellationToken cancellationToken = default)Parameters
Section titled “Parameters”record SessionRecord
The session to save.
cancellationToken CancellationToken
The cancellation token.
Returns
Section titled “Returns”The completion task.
TryCreateAsync(SessionRecord, CancellationToken)
Section titled “ TryCreateAsync(SessionRecord, CancellationToken)”Creates a new session record only if it does not already exist.
ValueTask<bool> TryCreateAsync(SessionRecord record, CancellationToken cancellationToken = default)Parameters
Section titled “Parameters”record SessionRecord
The session to create.
cancellationToken CancellationToken
The cancellation token.
Returns
Section titled “Returns”true if created; false if a record with the same identifier already exists.
Remarks
Section titled “Remarks”When two concurrent calls arrive with the same SessionRecord.Id, ONLY one must return true; the loser must get false and read the winner’s record with ISessionStore.GetAsync.
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 unconditionally overwrites the first, and the
loser’s messages become silently unreachable.
TryUpdateAsync(SessionRecord, long, CancellationToken)
Section titled “ TryUpdateAsync(SessionRecord, long, CancellationToken)”Replaces an EXISTING session record, but only if it has not changed since it was read.
ValueTask<bool> TryUpdateAsync(SessionRecord record, long expectedVersion, CancellationToken cancellationToken = default)Parameters
Section titled “Parameters”record SessionRecord
The new state of the session.
expectedVersion long
The SessionRecord.Version the caller read. A record read through ISessionStore.GetAsync or ISessionStore.QueryAsync carries it.
cancellationToken CancellationToken
The cancellation token.
Returns
Section titled “Returns”true if the record was replaced; false if it no longer exists or another writer changed it first.
Remarks
Section titled “Remarks”The sibling of ISessionStore.TryCreateAsync, and the reason both exist: ISessionStore.TryCreateAsync 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 overwrites unconditionally and cannot answer either question.
The write succeeds only when the stored record’s
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.