Skip to content

Concepts: Sessions & Hydration

A Session (sessionId) in Invariant is an optional durable continuity boundary that groups identity, hydrated application context, channels, and related workflow executions.

"Executions preserve progress within one Workflow run. Sessions preserve continuity across application activity."


1. What is a Session?

A Session is not a chat history transcript.

A Session is the durable container that allows multiple user interactions, channels, and workflow executions to belong to the same ongoing entity or business process.

A Session does not execute a Workflow and is not itself an Execution. It provides continuity around application activity that may span multiple executions.

text
                              Session

          ┌──────────────────────┼──────────────────────┐
          ▼                      ▼                      ▼
      Identity              Session Context          Channels
   sessionId / userId      hydrated business      web / voice / MCP
          │                      │                      │
          └──────────────────────┼──────────────────────┘

                    ┌────────────┴────────────┐
                    ▼                         ▼
             Workflow Executions         Agent Turns

Public Session API vs. Runtime Relationships

DimensionElementDescription
Public Session APIsession.idUnique durable session identifier (sess_101)
session.userIdUser or tenant identity associated with the session (usr_441)
session.contextTyped business context hydrated from external systems (TSessionContext)
Runtime RelationshipsIdentity BoundaryTies workflows and agent turns to a specific user or tenant
Attached TransportsDelivery channels (Web Chat, Gemini Live WebSocket, MCP)
Workflow ExecutionsActive and historical workflow runs associated with the session

2. Session ≠ Conversation

Sessions are not limited to conversational chat interactions. A Session can span multiple communication channels or run fully programmatic non-conversational automation:

Cross-Channel Customer Support

text
Web Chat (Session: sess_44) ──► Customer requests refund


Voice Call (Session: sess_44) ──► Customer calls support line; speaks to Live Voice Agent


Web Portal (Session: sess_44) ──► Customer reviews completed refund status online

Account Lifecycle Automation (Non-Conversational)

text
Webhook (Stripe Event)


   Session(account_123) ──► Starts Subscription Renewal Workflow

     later...

Cron (Nightly Job)


   Session(account_123) ──► Starts Billing Reconciliation Workflow

Both workflows share the same durable Session(account_123) context without any conversation or chat interface.


3. Hydration — Importing Authoritative Context

"Sessions do not ask the model to remember application truth. They hydrate it from authoritative systems."

When a new Session is created, Invariant invokes your application's hydrate() hook to fetch fresh, authoritative data from your CRM, database, or identity service—rather than forcing an LLM to reconstruct user identity from past message history. Existing Sessions load their durable context from storage without re-running hydration unless explicitly rehydrated (await session.rehydrate()).

ts
// src/runtime.ts
import { z } from "zod";
import { invariant } from "@invariant/sdk";

export const app = invariant({
  session: {
    contextSchema: z.object({
      customerId: z.string(),
      accountTier: z.enum(["FREE", "PRO", "ENTERPRISE"]),
      preferredLanguage: z.string(),
    }),

    hydrate: async ({ userId }) => {
      // Hydration is application-defined; it can compose data from multiple sources:
      const customer = await crm.getCustomer(userId);
      const billing = await stripe.customers.retrieve(customer.stripeId);

      return {
        customerId: customer.id,
        accountTier: billing.plan,
        preferredLanguage: customer.language,
      };
    },
  },
});

Hydration vs. Capabilities: Hydration is a Session lifecycle boundary, not a Workflow execution node. External reads performed during hydration establish Session Context; external I/O performed as part of Workflow execution belongs behind .capability() boundaries.

When you retrieve or create a session:

ts
const session = await app.sessions.getOrCreate("sess_101", "usr_441");

If no Session with that ID exists, Invariant creates it and runs the initial hydrate() pipeline. Existing Sessions are loaded from durable storage without invoking the hydrator again.

text
Identity (usr_441)


   hydrate()  ──► Composes CRM / Billing / Identity


Session Context
{
  customerId: "cust_91",
  accountTier: "PRO",
  preferredLanguage: "es"
}

      ├── Available to Workflows (({ session }) => ...)
      └── Projected to Agents (Agent Context Projection)

4. Rehydration — Refreshing Context

rehydrate() refreshes Session Context from its authoritative external sources when underlying business facts change.

For example, if a customer upgrades their subscription plan while a session remains active:

ts
// Customer upgrades plan in external billing portal...

await session.rehydrate();
text
Before rehydrate():
session.context.accountTier ──► "FREE"

CRM / Stripe updated:
customer.tier ──────────────► "PRO"

After session.rehydrate():
session.context.accountTier ──► "PRO"

Context Refresh Responsibility: Session Context remains durable until explicitly updated or rehydrated. Applications decide when external facts require a refresh.


5. Context Operations Summary

Invariant provides three distinct mechanisms for context management:

OperationTriggerSourcePurpose
hydrate()New session creationExternal systems (CRM / DB)Bootstraps initial Session Context
updateContext()Application codeDeveloper code mutationApplies explicit, validated context changes
rehydrate()Explicit refresh callExternal systems (CRM / DB)Refreshes context when external facts change

"updateContext() declares a context change. rehydrate() discovers one from authoritative sources."

ts
// Explicit application code mutation:
await session.updateContext({
  preferredLanguage: "en",
});

6. What Belongs in Session Context vs. Execution State?

It is essential to distinguish between Session Context and Workflow Execution State:

text
Session Context (sess_101)
customerId, accountTier, tenantId, permissions

        │ available across executions

Execution run_1                 Execution run_2
refund                          cancellation
────────                        ────────────
orderId                         subscriptionId
refundAmount                    effectiveDate
DimensionSession Context (session.context)Workflow Execution State (state)
ScopeLong-lived, independent from any individual executionFinite run of one specific workflow (runId)
Example DatacustomerId, accountTier, tenantId, permissionsorderId, refundEligibility, refundAmount, approvalStatus
NatureHydrated typed domain contextEvent-sourced accumulated state
LifecycleSurvives across multiple workflow runs & channelsResets for each new workflow execution

"Put information in Session Context when it should survive across related application activity. Put information in Execution State when it belongs to the progress of one Workflow run."

"Session Context represents durable continuity. Execution State represents durable progress."


7. Using Sessions with Agents & Workflows

With Agents

When an Agent turn executes (agent.run({ session, message })), Invariant combines session.context with the current RuntimeProjection to construct the model-facing payload:

ts
export const supportAgent = app.agent("support", {
  workflows: [refundWorkflow],
  context: ({ session }) => ({
    customerId: session.context.customerId,
    tier: session.context.accountTier,
  }),
});

With Programmatic Workflows

Programmatic workflows can access session context directly without involving an Agent or LLM:

ts
const run = await refundWorkflow.start({
  session,
  input: { orderId: "ord_99" },
});

8. Exact Session API Reference

ts
// Create or retrieve a durable session:
const session = await app.sessions.getOrCreate("sess_101", "usr_441");

// Read current context:
console.log(session.context.customerId);
console.log(session.context.accountTier);

// Update context explicitly:
await session.updateContext({ preferredLanguage: "es" });

// Rehydrate from external CRM/Stripe:
await session.rehydrate();

Go Deeper

  • Mental Model — Understand how Sessions fit into the 6 common execution paths.
  • Agents & Actions — Learn how AgentContextProjection combines Session Context with Runtime Projections.
  • Durable State & Events — Inspect event log persistence and state materialization.

Invariant Durable Execution Engine.