AWS Builder Center

Secure shared multi-tenant agent memory namespaces using AgentCore Memory

Implement secure shared multi-tenant agent memory namespaces using AgentCore Memory that maintain the same IAM-backed security as dedicated namespaces while using a single policy with Attribute-Based Access Control (ABAC). Instead of managing policies that multiply with each customer, you'll deploy once and scale effortlessly.

Managing memory storage for hundreds of customers shouldn't require hundreds of IAM policies. Yet most builders scaling multi-tenant agent applications find themselves trapped in exactly this scenario—creating dedicated namespaces and individual IAM policies for each tenant-agent combination. As your customer base grows from 10 to 100 to 1,000 tenants, your operational costs and deployment times grow proportionally.
The pain compounds across three isolation layers: you need to prevent tenant data from leaking across boundaries, keep agent memories separate, and maintain user-level privacy compliance. Traditional dedicated namespace approaches seem secure, but they create operational bottlenecks that can slow new customer onboarding from minutes to hours and inflate your infrastructure management costs by orders of magnitude.
The solution: Implement secure shared multi-tenant agent memory namespaces that maintain the same IAM-backed security as dedicated namespaces while using a single policy with Attribute-Based Access Control (ABAC). Instead of managing policies that multiply with each customer, you'll deploy once and scale effortlessly.

What you'll learn

This post demonstrates how to:
  • Secure shared Amazon Bedrock AgentCore Memory namespaces across multiple isolation dimensions using a single AWS IAM policy with Attribute-Based Access Control (ABAC)
  • Use your identity provider as a trusted source for tenant context
  • Enforce isolation dimensions with trust policies to prevent cross-tenant data leakage
  • Mitigate performance impact with effective caching strategies

Key benefits

By implementing shared namespaces with ABAC, you'll achieve:
  • Same IAM-backed security as dedicated namespaces
  • Constant operational costs for managing IAM policies and namespaces
  • Faster time to value for new customers by eliminating per-tenant namespace provisioning
  • Simplified management with fewer IAM policies to maintain at scale

Before you begin

This approach builds on tenant isolation patterns using AWS IAM with Attribute-Based Access Control (ABAC) and session tags. These techniques let you write policies once that work for all tenants by using dynamic attributes instead of hard-coded tenant identifiers. If you're new to this pattern, start with  How to implement SaaS tenant isolation with ABAC and AWS IAM  to understand the foundational concepts.

Shared memory, isolated tenants

A shared namespace simplifies operations, but you still need isolation so tenant data doesn't inadvertently leak across boundaries. You face three layers in this challenge: making sure that a tenant's long-term memories can't leak to another tenant, that one agent can't access another agent's memories, and that individual user memories comply with regulatory and privacy concerns.
For example, if you have two agents (travel-agent and shopping-agent) serving multiple tenants, then you need three dimensions of isolation. Tenant isolation ( tenantId ) means that Tenant A's travel preferences aren't visible to Tenant B. Agent isolation ( agentId ) ensures that the shopping agent can't access memories stored by the travel agent, even for the same tenant. Finally, user isolation ( userId ) keeps User 1's memories separate from User 2's, even within a tenant.
While application code can enforce boundaries, IAM policy means that AWS rejects unauthorized requests before they reach your code—providing defense in depth.

Simplify tenant isolation with AWS IAM and ABAC

Reduce the operational cost of managing your tenant-specific policies by creating a single IAM policy used across multiple agents, with per-agent roles that manage tenant and user credentials at runtime using ABAC.
Start by partitioning your data in a way that can be restricted by an IAM policy using the isolation dimensions as ABAC attributes. Using ABAC to replace tenant-specific metadata, such as tenantId, reduces the number of IAM policies you need to manage at scale. This example policy is for Amazon DynamoDB where rows are tenant-partitioned, and IAM enforces that a caller can only access their own tenant's data:
JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:Query"
      ],
      "Resource": "arn:aws:dynamodb:eu-central-1:123456789012:table/Orders",
      "Condition": {
        "ForAllValues:StringEquals": {
          "dynamodb:LeadingKeys": ["${aws:PrincipalTag/TenantId}"]
        }
      }
    }
  ]
}
This pattern can handle multiple isolation dimensions. For example, agent level isolation using tenantId and agentId, or user level isolation using tenantId, agentId, and userId. 

Securing a shared namespace 

Organize your agent memories efficiently with customizable namespace paths that scale as your customer base grows. AgentCore Memory structures long-term memory with a customizable namespace path that includes pre-defined variables (actorId, memoryStrategyId, and sessionId) you can reference in IAM policy condition keys. Use the actorId variable as a composite of your isolation dimensions to enforce isolation boundaries using IAM. For example, implement user level isolation with the following composite:
TypeScript
actorId = "{tenantId}:{agentId}:{userId}"
When tenant-beta's user f36458b2 interacts with the travel agent:
TypeScript
actorId = "tenant-beta:travel-agent:f36458b2"
namespace = "/tenant-beta:travel-agent:f36458b2/my_memory/"
How you restrict your memory actions depends on the condition keys supported by the actions. To control who can create and modify events, use the actorId condition with event actions (CreateEvent, GetEvent, ListEvents, DeleteEvent). For retrieval actions (RetrieveMemoryRecords, ListMemoryRecords), use the namespace condition. This requires two different IAM statements.

Here's an IAM policy to isolate user memories in a namespace:
JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Condition": {
"StringLike": {
"bedrock-agentcore:actorId": "${aws:PrincipalTag/tenantId}:${aws:PrincipalTag/agentId}:${aws:PrincipalTag/userId}"
}
},
"Action": [
"bedrock-agentcore:CreateEvent",
"bedrock-agentcore:GetEvent",
"bedrock-agentcore:ListEvents",
"bedrock-agentcore:DeleteEvent"
],
"Resource": "arn:aws:bedrock-agentcore:eu-central-1:123456789012:memory/my_memory",
"Effect": "Allow"
},
{
"Condition": {
"StringLike": {
"bedrock-agentcore:namespace": "/${aws:PrincipalTag/tenantId}:${aws:PrincipalTag/agentId}:${aws:PrincipalTag/userId}/*"
}
},
"Action": [
"bedrock-agentcore:RetrieveMemoryRecords",
"bedrock-agentcore:ListMemoryRecords"
],
"Resource": "arn:aws:bedrock-agentcore:eu-central-1:123456789012:memory/my_memory",
"Effect": "Allow"
}
]
}

Attach this policy to an IAM role for each agent that needs access to the namespace. This role is used by the agent at runtime, and is separate from the agent  execution role .

Securing agent roles

You can secure the agent roles using your namespace policy with a trust policy. The trust policy of the role enforces that only the execution role of the agent is allowed to assume it, and that the expected session tags are present. It also checks that agentId matches the agent the policy was created for (agent level isolation), to prevent one agent from accidentally or unintentionally assuming another agent's role, even if they have the same execution role. For example, the trust policy of a role for the shopping agent uses the StringEquals condition to match the AgentId, and StringLike condition to enforce that TenantId and UserId are present.
JSON
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::123456789012:role/MyAgentExecutionRole"
            },
            "Action": [
                "sts:AssumeRole",
                "sts:TagSession"
            ],
            "Condition": {
                "StringEquals": {
                    "aws:RequestTag/AgentId": "shopping-agent"
                },
          StringLike: {
            'aws:RequestTag/TenantId': '?*',
            'aws:RequestTag/UserId': '?*',
          },
            }
        }
    ]
}

Accessing the namespace

You can access the namespace using temporary security credentials scoped with isolation attributes. The agent is deployed with  least-privilege permissions  to perform it’s function. At runtime, the agent  assumes the role  with the isolation policy and passes tenantId, agentId , and userId as  session tags  to get credentials scoped to access the memory. Marking the session tags as  transitive  marks them as immutable if the resulting credentials are used to assume another role.

The following code snippet shows how an agent runtime creates a scoped  BedrockAgentCoreClient  client using the AWS SDK  fromTemporaryCredentials()  function that retrieves temporary credentials from the AWS Security Token Service (STS)  AssumeRole API  with the appropriate session tags. This example shows implementation in a Node.js AgentCore runtime.
TypeScript
const memoryClient = new BedrockAgentCoreClient({
  region: REGION,
  credentials: fromTemporaryCredentials({
    params: {
      RoleArn: TVM_ROLE_ARN,
      RoleSessionName: `tenant-${tenantId.replace(/[^\w+=,.@-]/g, '_').substring(0, 50)}`,
      Tags: [
        { Key: 'TenantId', Value: tenantId },
        { Key: 'UserId', Value: userId },
        { Key: 'AgentId', Value: AGENT_ID },
      ],
      TransitiveTagKeys: ['TenantId', 'UserId', 'AgentId'],
      DurationSeconds: 900,
    },
  }),
});
At deployment, you supply agentId as an environment variable. If agentId is unintendendly changed during deployment, then impersonation of another agent is secured by the trust policy of the assumed role.

You need a trusted source for tenantId, and userId. Consider using a JSON Web Token (JWT) with  inbound authentication with JWT bearer tokens and AgentCore Runtime  for multi-tenant agents. This can  pass the access token used for authentication to the agent , so you can get user attributes from the identity provider.

With this approach, the identity provider is a trusted source, so you need to own it in order to have control over the  custom user attributes  you use for multi-tenancy, such as tenantId. Cognito and most identity providers use the sub attribute as an immutable  unique user ID , making it a good substitute for userId because it will not change in a user’s lifetime.

However, custom attributes are not included into access tokens by default.  The access token can be customized  for its inclusion with a mechanism like  Cognito pre-token generation . Here is a snippet for adding the tenantId from user attributes, to be used in a Cognito pre-token generation Lambda function:
TypeScript
export const handler: PreTokenGenerationV2TriggerHandler = async (event) => {
  const tenantId = event.request.userAttributes['custom:tenantId'];
  if (!tenantId) throw new Error('User is missing custom:tenantId attribute');
 
  event.response.claimsAndScopeOverrideDetails = {
    accessTokenGeneration: {
      claimsToAddOrOverride: { tenantId },
    },
  };
 
  return event;
};


Solution overview


The end-to-end process works as follows.
Overview of the end to end process

1.     User authenticates against Amazon Cognito as an identity provider.
2.     Cognito calls a pre token generation Lambda function which modifies the access token to include the tenantId claim and returns the token to the user.
3.     User invokes the agent runtime using the token as proof of authentication.
4.     The agent extracts tenantId and userId from the token, subsituting sub (used by Cognito as an immutable user ID) for userId.
5.      AWS Security Token Service (AWS STS)  is called by the agent to assume an agent specific role which has permissions to access the namespace, scoped with an actorId made as a composite of the isolation dimensions (tenantId, agentId, userId).  The agent role has agentId hard-coded to it’s trust policy, while tenantId and userId are passed as session tags to AssumeRole.
6.     The resulting temporary security credentials have actorId scoped to the user’s namespace key.
7.     The agent accesses the user’s memory records using the scoped credentials.


Performance and resilience


You may require caching to mitigate the performance impacts and improve resiliency. You should consider the following in your caching strategy:
  • Performance impact   The AWS Security Token Service (AWS STS) AssumeRole command will introduce latency of ~50-200ms depending on configuration. This may be acceptable depending on the agent use-case.
  •  Token lifecycle            Token refresh  needs to be managed for long-running sessions, when the credential has reached ~75% of the token lifetime.
  •  Throttling                   AWS STS has a default quota of  600 requests per second  per region, per account, for a number of operations shared with AssumeRole.

When you implement a multi-tenancy cache, the cache key should be the same format as the actorId used in the namespace. Use the token as the source for the cache key because it has already been  validated by AgentCore  during inbound authentication.

AWS SDKs provide features that can help you implement a caching strategy with standardized features like  retry behaviours  to handle throttling, and an  assume role credential provider  which will attempt to  renew credentials automatically when they expire . You can cache instances of the BedrockAgentCoreClient client, each scoped with the correct actorId, and the SDK will handle token refresh.

Remember to monitor  service quotas  to know when AWS STS is throttling requests. Using a  Regional endpoint  is recommended because  AWS STS request quotas  are per account, per Region. Likewise, monitor for access violations of AssumeRole in  AWS CloudTrail , and implement end-to-end testing of isolation mechanisms as part of deployment. Common errors include "StringLike condition not matching", "AssumeRole access denied", and "Missing session tags".

As a security best practice, never log temporary credentials. They should remain in memory only and be discarded after use.

Summary

Multi-tenant agents need verifiable memory isolation to safely serve multiple customers. At the same time, dedicated namespaces per-tenant, agent, and user may lead to scaling bottlenecks due to operational overhead.

This post demonstrated how to secure shared AgentCore Memory namespaces using ABAC and AssumeRole. This solution provides a shared memory namespace with the same IAM-backed security as dedicated namespaces.

A single IAM policy for the namespace can be shared across multiple agents, while enforcing agent isolation boundaries using restricted trust permissions on per agent roles using this policy. With an effective caching strategy, this approach scales to support a large dynamic number of agents with little overhead.

The pattern here using IAM condition keys and session tags with ABAC also apply to additional isolation dimensions or other resources. It is important that the source of these attributes at runtime is trusted, either from infrastructure-as-code or a trusted authentication token supplied by a user.

Next steps


If you are scaling your multi-tenant agent application and looking for operational efficiencies, consider implementing this pattern as a proof of concept, otherwise consider using a shared namespace as you create multi-tenant agents which are intended to scale.

Start by defining your isolation boundaries, do you need user or tenant level isolation? Build a composite actorId from your isolation dimensions. Then, create a single IAM policy which restricts access to the namespace using an actorId created by your isolation attributes passed as session tags during AssumeRole. Finally, create a role per agent with a trust policy that is scoped to the agent.

The following serves as reference content:

  • Links to relevant AWS documentation:
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article