はじめに
エージェントの長期記憶(メモリ)をどうやってテナントごとに分離するか
Amazon Bedrock AgentCore Memoryは、エージェントの会話から長期記憶を自動抽出してくれる便利な機能ですが、これまでは記憶の整理に使える軸が以下の通りでした。
-
actorId(誰の記憶か) -
sessionId(どの会話か) -
memoryStrategyId(どの抽出戦略か)
「組織」や「チーム」「環境」といった、アプリケーション固有の軸でメモリを分離したい場合は、ちょっと工夫が必要だったということです。
2026年8月28日、この制約を解消する カスタム名前空間変数(Custom Namespace Variables) が発表されました。
カスタム名前空間変数とは
Before
これまでのAgentCore Memoryの名前空間は、次のような組み込み変数の組み合わせでしか階層を作れませんでした。
/strategy/{memoryStrategyId}/actor/{actorId}/session/{sessionId}/
例えば「会社Aのエンジニアチーム」と「会社Bの営業チーム」のメモリを分けたい、というマルチテナント要件があっても、actorId(ユーザー単位)や memoryStrategyId(戦略単位)を無理やり流用するか、テナントごとにメモリリソース自体を分けるしかありませんでした。前者は本来の意味とズレた使い方になってしまいますし、後者は管理コストが跳ね上がります。
After
今回のアップデートで、namespaceKeys というパラメータを使い、メモリリソース作成・更新時に独自のキーを宣言できるようになりました。宣言したキーは namespaceTemplate の中で {orgname} のように埋め込んで使えます。
/org/{orgname}/team/{teamname}/actor/{actorId}/session/{sessionId}/
そして実際の値は、会話イベントを記録する CreateEvent API呼び出し時に extractionConfig.namespaceVariables として渡します。つまりテンプレートはリソース作成時に固定し、値は実行時に流し込むということのようです。
何がうれしいのか
- マルチテナントSaaS: 同じメモリリソース・同じ戦略を使い回しながら、顧客企業ごと・部署ごとに記憶を分離できる
-
IAMによる書き込みガード:
bedrock-agentcore:namespaceVariable/orgnameを条件キーに使ったIAMポリシーで、「Acme社用の認証情報ではAcme社のnamespaceVariableでしか書き込めない」という制御が可能になる
やってみた
手順1: Terraformでメモリリソースを作る
Terraformが担当できるのはメモリリソース本体までです。
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
resource "aws_bedrockagentcore_memory" "tenant" {
name = "MultiTenantAgentMemory"
description = "組織・チーム単位でメモリを分離するデモ"
event_expiry_duration = 7
}
output "memory_id" {
value = aws_bedrockagentcore_memory.tenant.id
}
terraform init
terraform apply -auto-approve
Outputs:
memory_id = "MultiTenantAgentMemory-AoXZUcA706"
以降のスクリプトで使い回すので、メモリIDを環境変数に入れておきます。
export MEMORY_ID=$(terraform output -raw memory_id)
export AWS_REGION=ap-northeast-1
echo $MEMORY_ID
手順2: 戦略とキーをAPIで一括作成する
戦略と namespaceKeys は 1回の UpdateMemory でまとめて作ります。
import os, time, boto3
memory_id = os.environ["MEMORY_ID"]
control = boto3.client("bedrock-agentcore-control")
template = "/org/{orgname}/team/{teamname}/actor/{actorId}/session/{sessionId}/"
control.update_memory(
memoryId=memory_id,
namespaceKeys=[
{"key": "orgname", "validation": {"allowedValues": ["acme", "globex"]}},
{"key": "teamname", "validation": {"regexPattern": "^[a-z][a-z0-9-]*$"}},
],
memoryStrategies={"addMemoryStrategies": [
{"semanticMemoryStrategy": {
"name": "TenantScopedStrategy",
"namespaceTemplates": [template],
}}
]},
)
while True:
m = control.get_memory(memoryId=memory_id)["memory"]
if m["status"] == "ACTIVE" and all(s["status"] == "ACTIVE" for s in m["strategies"]):
break
time.sleep(10)
print("ready:", [(s["name"], s["status"]) for s in m["strategies"]])
orgname(許可リストで検証)と
teamname(正規表現で検証)の
2つのカスタムキーが、セマンティック戦略の namespaceTemplates と一緒に登録されました。
なお、キーも値も小文字のみである点に注意が必要です。
手順3: 異なる組織のイベントを作成する
acme 社と globex 社、それぞれのユーザーから会話イベントを投げてみます。extractionConfig.namespaceVariables に組織・チーム名を渡すのがポイントです。
import os, time, boto3
memory_id = os.environ["MEMORY_ID"]
data = boto3.client("bedrock-agentcore")
cases = [
("acme", "engineering", "user-001", "私はダークモードが好きで、スクリプトはPythonで書きます。"),
("globex", "platform", "user-002", "私はライトモードが好きで、バックエンドはGoで書きます。"),
]
for org, team, actor, text in cases:
ev = data.create_event(
memoryId=memory_id,
actorId=actor,
sessionId=f"session-{org}-1",
eventTimestamp=time.time(),
payload=[{"conversational": {"content": {"text": text}, "role": "USER"}}],
extractionConfig={"namespaceVariables": {"orgname": org, "teamname": team}},
)
print(org, ev["event"]["eventId"])
acme 0000001789201248188#0f6f8f5d
globex 0000001789201248637#1f06965d
結果確認: 名前空間ごとに記憶が分離されているか
長期記憶の抽出は非同期処理なので、少し待ってから ListMemoryRecords で確認します。
import os, boto3
memory_id = os.environ["MEMORY_ID"]
data = boto3.client("bedrock-agentcore")
for org, team, actor in [("acme", "engineering", "user-001"), ("globex", "platform", "user-002")]:
namespace = f"/org/{org}/team/{team}/actor/{actor}/session/session-{org}-1/"
records = data.list_memory_records(memoryId=memory_id, namespace=namespace, maxResults=5)
print(namespace)
for r in records["memoryRecordSummaries"]:
print(" -", r["content"]["text"])
数分後、きれいに分離されました。
/org/acme/team/engineering/actor/user-001/session/session-acme-1/
- ユーザーはスクリプトをPythonで書く。
- ユーザーはダークモードが好きである。
/org/globex/team/platform/actor/user-002/session/session-globex-1/
- ユーザーはバックエンドをGoで書く。
- ユーザーはライトモードが好きである。
同じメモリリソース内でもテナントの記憶が混ざらないことを確認できました。
後片付け
検証が終わったら忘れずに片付けておきます。
terraform destroy -auto-approve
参考リンク
- AWS What's New: Amazon Bedrock AgentCore Memory now supports flexible namespace variables
- Specify long-term memory organization with namespaces(AgentCore デベロッパーガイド)
- Memory organization in AgentCore Memory
- Memory terminology
- Organizing Agents' memory at scale: Namespace design patterns in AgentCore Memory(AWS Machine Learning Blog)
