feat(ts#agent): add session management support - #1017
Conversation
S3Storage lazily imports @aws-sdk/client-s3 at runtime; without it declared, the bundler leaves the import unresolved and it fails in the deployed container, which only ships the bundled output (no node_modules).
Matches the codebase's dash-separated multi-word option convention (ag-ui, cloudfront-s3, etc.) and reads more clearly at the call site than 'none', which could be mistaken for "no session support at all" rather than "session kept in process memory only".
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1017 +/- ##
==========================================
- Coverage 88.53% 88.43% -0.10%
==========================================
Files 179 180 +1
Lines 6666 6842 +176
Branches 1619 1672 +53
==========================================
+ Hits 5902 6051 +149
- Misses 364 371 +7
- Partials 400 420 +20 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| ```typescript | ||
| export const getSessionManager = async (): Promise<SessionManager> => { | ||
| const sessionId = getCurrentSessionId(); | ||
| // local development always uses local file storage... | ||
| // ...otherwise S3Storage (session: 's3') or InMemoryStorage (session: 'in-memory') | ||
| }; | ||
| ``` |
There was a problem hiding this comment.
Nit: probably don't need this example :)
| autoDeleteObjects: true, | ||
| removalPolicy: RemovalPolicy.DESTROY, |
There was a problem hiding this comment.
While useful for testing, I think it's safer not to delete data on destroy, like we do for databases :)
| suppressRules( | ||
| sessionBucket, | ||
| ['CKV_AWS_145'], | ||
| 'AES256 (S3-managed) encryption is sufficient for session data', | ||
| ); |
There was a problem hiding this comment.
Probably better to use a CMK :)
| suppressRules( | ||
| sessionBucket, | ||
| ['CKV_AWS_18'], | ||
| 'Access logging not required for session data', | ||
| ); |
There was a problem hiding this comment.
I think we should follow the best practice and store access logs in cloudwatch like for other buckets :)
| #checkov:skip=CKV2_AWS_61:Lifecycle configuration not required for session data | ||
| #checkov:skip=CKV_AWS_144:Cross-region replication not required for session data | ||
| #checkov:skip=CKV2_AWS_62:Event notifications not required for session data | ||
| #checkov:skip=CKV_AWS_18:Access logging not required for session data | ||
| #checkov:skip=CKV_AWS_21:Session data does not need versioning enabled | ||
| #checkov:skip=CKV_AWS_145:AES256 (S3-managed) encryption is sufficient for session data | ||
| force_destroy = true |
There was a problem hiding this comment.
Same comments as above for s3, best to retain data, use a cmk and store s3 access logs in cloudwatch :)
| // Local-dev session storage lives at the workspace root | ||
| // (`tmp/agents/strands/<agent-name>`), not inside the project, so each agent | ||
| // gets its own storage directory. The `-dev`/`-serve` targets run with | ||
| // cwd={projectRoot}, so compute that directory relative to the project root | ||
| // here rather than resolving it at runtime (e.g. via import.meta.url), | ||
| // which would need an extra runtime helper. | ||
| const projectDepth = project.root.split('/').filter(Boolean).length; | ||
| const localSessionsDir = joinPathFragments( | ||
| Array(projectDepth).fill('..').join('/'), | ||
| `tmp/agents/strands/${name}`, | ||
| ); |
There was a problem hiding this comment.
Nit: you can use getRelativePathToRootByDirectory :)
| * Plugin that logs model invocation errors, calling out missing credentials or | ||
| * denied permissions. | ||
| */ | ||
| export class ModelErrorLoggingPlugin implements Plugin { |
There was a problem hiding this comment.
actually HTTP/A2A should also use the plugin form instead of calling log*Errors, let me update it
| <%- nameClassName %>: { | ||
| arn: this.agentCoreRuntime.agentRuntimeArn, | ||
| session: { | ||
| storage: '<%- session %>', |
There was a problem hiding this comment.
Do we need to store storage? I might have missed it but it doesn't look like anything actually checks this, as the getSessionManager is generated to look for the bucketName regardless :)
There was a problem hiding this comment.
ah actually no, it is just metadata. removed to simplify the config
| // A string value is a local-dev override URL; otherwise it's the | ||
| // deployed runtime entry, so build the invocation URL from its ARN. | ||
| typeof agentRuntimeValue === 'string' | ||
| ? agentRuntimeValue | ||
| : buildAgentCoreUrl(agentRuntimeValue.arn), |
There was a problem hiding this comment.
Do we get a type when the connection generator sets the value which is meant to be an object as a string? :)
There was a problem hiding this comment.
I also realised that the shape of the client side connection doesn't have to follow the shape of the server side agentcore config so it can remain as agentRuntimes: string. But maybe it's a good idea to just refactor it into an object agentRuntimes: { arn: string } to be consistent and in case we have to include other settings in the future?
There was a problem hiding this comment.
Ooh yes true- I think it might be best to keep as a string to keep it simple, we can always refactor later if needed :) probably better to have a bit of friction to do so too as we ideally don’t want to send the bucket name to the front end :)

Reason for this change
ts#agent-generated agents had no built-in way to persist conversation state (message history, tool state) across invocations. Since Bedrock AgentCore Runtime can scale or restart between requests, this meant conversations couldn't survive beyond a single process lifetime.Description of changes
sessionoption tots#agent:s3(default) orin-memory.session.tsexportinggetSessionManager(), wired intoagent.tsfor HTTP/A2A agents (directly on theAgentconstructor) and intoindex.tsfor AG-UI agents (viasessionManagerProvider, since AG-UI clones a template agent per thread).session: s3, CDK/Terraform provision a dedicated S3 bucket (SSE-S3 encrypted, all public access blocked) and grant the agent's IAM role read/write access; the bucket name is registered in AppConfig alongside the agent's runtime ARN.LocalFileStorageundertmp/agents/strands/<agent-name>at the workspace root, regardless of the configuredsessionoption.@aws-sdk/client-s3as an explicit dependency whensession: s3, so the bundler can resolve the Strands SDK's lazyimport('@aws-sdk/client-s3')— without it, the deployed container (which ships nonode_modules) fails at runtime.ts-agent-session-management-supportNx migration to bring existing (pre-feature) workspaces up to the new shape: reshapesagentRuntimesentries to{ arn, session }, backfillssession.tsand wires it into existing agents, and migrates the AG-UI error-logging hooks toStrandsAgentplugins.py#agentalso gains thesessionoption (schema only, alwaysin-memory) for API parity — Python session manager support isn't implemented yet.ts#agentguide; fixed staleFileTree/code samples that predated this change.Description of how you validated changes
ts#agent/py#agent, including MCP server, A2A, and React website connections, in this commit.session.tsbackfilled and wired in, error-logging hooks migrated toStrandsAgentplugins for the AG-UI agent,agentRuntimesreshaped, and connection client/provider code updated to read the new{ arn, session }shape.session: s3, an A2A connection between them, and wired them to CDK infra in this commit, deployed to AWS, and verified the sessionId flows through end-to-end — each connected agent persists its session under the same sessionId, in its own dedicated S3 bucket. Also chatted with the agents locally to verify each agent's local session directory (tmp/agents/strands/<agent-name>) is created and populated correctly.Issue # (if applicable)
Closes #.
Checklist
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license