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.
sessionAttributes | promptSessionAttributes | |
|---|---|---|
| Lifetime | The whole session | A single turn |
| Reaches the Lambda | Yes | Yes |
| Reaches the model's prompt | No | Yes, via the $prompt_session_attributes$ placeholder in the orchestration template |
| Typical use | Identity and context your code needs: customer id, tenant, locale, correlation id | Facts 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
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