InMemorySessionStore
AgentPrism.Core.dllA store that keeps sessions in process memory.
public sealed class InMemorySessionStore : ISessionStoreInheritance
Section titled “Inheritance”Implements
Section titled “Implements”Inherited Members
Section titled “Inherited Members”object.GetType(), object.ToString(), object.Equals(object?), object.Equals(object?, object?), object.ReferenceEquals(object?, object?), object.GetHashCode()
Remarks
Section titled “Remarks”The limits are the same as InMemoryAgentDefinitionStore:
process lifetime and a single node. Use AgentPrism.PostgreSql in production.
Sessions are separated per tenant; the tenant is read from ITenantContext.
Constructors
Section titled “Constructors”InMemorySessionStore(ITenantContext?)
Section titled “ InMemorySessionStore(ITenantContext?)”Creates a new in-memory session store.
public InMemorySessionStore(ITenantContext? tenantContext = null)Parameters
Section titled “Parameters”tenantContext ITenantContext?
The current tenant’s context. If not given, the store behaves as single-tenant.
Methods
Section titled “Methods”DeleteAsync(string, CancellationToken)
Section titled “ DeleteAsync(string, CancellationToken)”Deletes the session.
public 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.
public 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.
public 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.
public 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.
public 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.
Remarks
Section titled “Remarks”If a record with the same identity exists, SessionRecord.CreatedAt is preserved. The “creation time” belongs to the first write; the persistent store shows the same behavior.
TryCreateAsync(SessionRecord, CancellationToken)
Section titled “ TryCreateAsync(SessionRecord, CancellationToken)”Creates a new session record only if it does not already exist.
public 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.