See why a session is more than chat history
The booking fixture returns a selection in one call and receives the user's choice in another. The runtime must remember the original API items, expiry, dependency frame, and later the exact prepared request. Reusing the same sessionId connects those calls.
Creating a new runtime for every message with its own memory store loses that state. Two independent workers with separate memory stores also cannot reliably continue each other's choices. Use one long-lived instance for a single-process app; for multiple workers, provide shared stores with the transaction contract below.
Choose the storage behavior before scaling
A shared key/value record is not enough. Two simultaneous confirmations must not both read the same approval before either consumes it. transact() must serialize the entire asynchronous operation for one session, including awaited network work. A lock lease must not silently expire while that work is still active.
Session state and retained credentials are separate stores. Sharing SessionStore alone does not make a token retained by worker A available to worker B. Either pass the authorized token on each continuation or supply the shared encrypted token-store configuration described in authentication.
Test this with two runtime instances, one shared store, and concurrent confirmation calls. Assert one write. Also test expiry, config change, and a worker interruption. A storage outage is an operational failure, not permission to replay a business effect.
What lives in a session
SessionStateV2 stores:
state— explicit runtime state;history— bounded user/assistant messages;facts— sourced values with optional TTL;stack— dependency execution frames;- pending
selection; - pending
confirmation; - sanitized host
context; referencespublished from selected API items;configHash;- expiry and lifetime message count.
Transaction contract
The critical SessionStore method is not get() or set(). It is:
transact<T>(id: string, operation: () => Promise<T>): Promise<T>
The contract requires serialization of the entire async operation for one session across all runtime instances sharing that store.
A naive Redis implementation that does:
GET session
...
SET session
without a renewable distributed lock/fencing mechanism is not sufficient for multi-instance correctness.
Memory store
import { MemorySessionStore } from "agento-runtime";
Good for:
- local development;
- tests;
- one-process deployments where runtime instances share the same store object.
Not enough for horizontal multi-process deployments.
Custom store interface
interface SessionStore {
get(id: string): Promise<SessionStateV2 | null>;
set(id: string, state: SessionStateV2): Promise<void>;
delete(id: string): Promise<void>;
touch(id: string, expiresAt: number): Promise<void>;
transact<T>(id: string, operation: () => Promise<T>): Promise<T>;
}
Expiry and config changes
A session becomes invalid if its TTL expires or its stored configHash no longer matches the compiled runtime configuration. AGENTO clears incompatible state instead of trying to continue an operation under different rules.
Interrupted execution marker
If a stored session is found in EXECUTING, AGENTO treats the previous effectful operation as uncertain rather than automatically replaying it. Reconcile first.
Host context
Pass trusted request/session metadata:
context: {
tenant: { id: "tenant_42" },
locale: "ar-PS"
}
Then map explicitly:
X-Tenant-Id:
source: session
path: $.tenant.id
Do not put access tokens in ordinary context; use auth.token.