Session State: Session Attributes vs Prompt Attributes

Session state is the side channel you use to pass data into an agent turn — session attributes reach your Lambda, prompt attributes reach the model, and the two are not interchangeable.

A Bedrock agent conversation is identified by a sessionId you invent. The service keeps the turns for that session and replays them into the prompt, and the session expires after the idle window set by idleSessionTTLInSeconds (600 seconds by default). Alongside that automatic history, InvokeAgent accepts a sessionState object — the structured side channel for everything the conversation needs but the user did not type.

The two attribute bags

They look identical in the API and behave completely differently.

sessionAttributespromptSessionAttributes
LifetimeThe whole sessionA single turn
Reaches the LambdaYesYes
Reaches the model's promptNoYes, via the $prompt_session_attributes$ placeholder in the orchestration template
Typical useIdentity and context your code needs: customer id, tenant, locale, correlation idFacts the model must reason with right now: today's date, the user's timezone, the page they are on

The rule of thumb: if the model needs to know it, it must be a prompt attribute. If only your code needs it, make it a session attribute so it stays out of the context window. The classic failure is putting the customer id in promptSessionAttributes — now the model can see it, will happily repeat it back, and may pass a different one to a tool. Put it in sessionAttributes, have the Lambda read it from the event, and ignore any id the model tries to supply. That is not just tidiness; it is the boundary that stops a user talking the agent into acting on someone else's account.

The rest of session state

sessionState also carries invocationId and returnControlInvocationResults for the return-of-control handshake, files for code interpreter or chat-with-attachment flows, knowledgeBaseConfigurations to override retrieval settings for one call, and conversationHistory, which lets you seed a brand-new session with turns that happened elsewhere — useful when a conversation starts in your own stack and is handed to the agent mid-thread.

Session state is not memory

This distinction is examinable. Session state is per-invocation input that you supply and that dies with the session. Conversation history is the turns inside one session, managed for you. Memory is the opt-in feature you enable with memoryConfiguration and enabledMemoryTypes=["SESSION_SUMMARY"], addressed by a memoryId; it summarises finished sessions and carries that summary across sessions for the same end user, with a configurable retention period. And a knowledge base is none of those — it is retrieval over documents, not recall of a conversation.

A Lambda can also write attributes back. Returning sessionAttributes or promptSessionAttributes in the action response updates them for the rest of the session, which is how a tool records something like a verified identity flag once and lets later tools trust it.

Example

Example · python
resp = rt.invoke_agent(
    agentId=AGENT_ID,
    agentAliasId=PROD_ALIAS_ID,
    sessionId="web-9f2c-thread-1",
    memoryId="customer-88421",          # cross-session summary, separate from sessionState
    inputText="Has my claim moved on since we last spoke?",
    enableTrace=True,
    sessionState={
        # Code-only: the model never sees these
        "sessionAttributes": {
            "customer_id": "88421",
            "tenant": "acme-uk",
            "auth_level": "verified",
            "correlation_id": "7c1f...",
        },
        # Model-visible for this turn only
        "promptSessionAttributes": {
            "today": "2026-08-26",
            "timezone": "Europe/London",
            "channel": "mobile app",
        },
    },
)

# In the action Lambda: trust the session attribute, never the model's argument
def handler(event, context):
    customer_id = event["sessionAttributes"]["customer_id"]      # authoritative
    params = {p["name"]: p["value"] for p in event.get("parameters", [])}
    claim_ref = params["claim_reference"]

    if not owns_claim(customer_id, claim_ref):
        body = "That claim is not on this account."
        state = "REPROMPT"
    else:
        body = json.dumps(load_claim(claim_ref))
        state = None

    fr = {"responseBody": {"TEXT": {"body": body}}}
    if state:
        fr["responseState"] = state
    return {"messageVersion": "1.0",
            "response": {"actionGroup": event["actionGroup"],
                         "function": event["function"],
                         "functionResponse": fr}}

When to use it

  • Passing the signed-in user's id and tenant to every action without letting the model see or alter them.
  • Telling the agent today's date and the user's timezone so relative phrases like 'by Friday' resolve correctly.
  • Resuming an agent conversation that began in your own chat stack by seeding conversationHistory on the first invoke.

Discussion

  • Be the first to comment on this lesson.