strands-s3-vectors-memory 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ blog_s3_vector_memory
2
+ .pytest_cache
3
+ strands_feature
4
+ backup
5
+ __pycache__
6
+ .kiro
7
+ report.html
8
+ .venv-perf
9
+ .env.performance
@@ -0,0 +1,474 @@
1
+ Metadata-Version: 2.4
2
+ Name: strands-s3-vectors-memory
3
+ Version: 0.1.0
4
+ Summary: Long-term semantic memory plugin for Strands Agents backed by Amazon S3 Vectors
5
+ Project-URL: Homepage, https://github.com/aws-samples/data-for-saas-patterns/tree/main/samples/multi-tenant-strands-s3-vectors-memory
6
+ Project-URL: Documentation, https://github.com/aws-samples/data-for-saas-patterns/tree/main/samples/multi-tenant-strands-s3-vectors-memory#readme
7
+ Project-URL: Repository, https://github.com/aws-samples/data-for-saas-patterns
8
+ Project-URL: Issues, https://github.com/aws-samples/data-for-saas-patterns/issues
9
+ License: MIT
10
+ Keywords: agents,aws,bedrock,memory,s3,strands,vectors
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Libraries
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: boto3>=1.34.0
21
+ Requires-Dist: cachetools>=5.3.0
22
+ Requires-Dist: strands-agents>=0.1.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest-anyio>=0.0.0; extra == 'dev'
25
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # S3 Vector Memory Plugin for Strands Agents
29
+
30
+ A [Strands Plugin](https://strandsagents.com/docs/user-guide/concepts/plugins/) that gives any Strands Agent long-term semantic memory backed by [Amazon S3 Vectors](https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors.html). At the end of a conversation, the plugin summarizes the full exchange using the agent's own model and stores the summary as a searchable vector. On subsequent conversations, relevant summaries are retrieved and injected into the system prompt before the LLM responds.
31
+
32
+ Available in two modes:
33
+
34
+ - **Single-tenant** — one shared index, no extra IAM setup
35
+ - **Multi-tenant** — one index per tenant, IAM credentials scoped per tenant via the [Token Vending Machine (TVM)](https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/implement-saas-tenant-isolation-for-amazon-s3-by-using-an-aws-lambda-token-vending-machine.html) pattern
36
+
37
+ ![Architecture diagram](images/s3_vector_memory.png)
38
+
39
+ ---
40
+
41
+ ## Motivation
42
+
43
+ LLMs are stateless. Every conversation starts from scratch — the model has no memory of who the user is, what they've discussed before, or what decisions were made in past sessions. For most production agents, this is a serious limitation.
44
+
45
+ The standard workaround is to stuff conversation history into the context window. That works for a single session, but it doesn't scale:
46
+
47
+ - **Context windows are finite.** Long histories get truncated. Important context from weeks ago disappears.
48
+ - **Cost grows linearly.** Sending the full history on every turn means paying to re-process the same tokens repeatedly.
49
+ - **Cross-session recall is impossible.** When a user returns days later, the previous conversation is gone.
50
+
51
+ Vector databases solve the recall problem, but they introduce new complexity — especially in multi-tenant SaaS applications where tenant data must be strictly isolated. Most vector stores don't have native IAM-level isolation, so you end up building custom access control on top, which is error-prone and hard to audit.
52
+
53
+ This plugin solves all three problems:
54
+
55
+ 1. **Persistent memory across sessions.** At the end of each conversation, the plugin summarizes the exchange and stores it as a vector. On the next conversation, relevant summaries are retrieved and injected into the system prompt — the agent "remembers" without bloating the context window.
56
+
57
+ 2. **Semantic retrieval, not keyword search.** Memories are retrieved by meaning, not exact match. A user asking "what was my budget?" will surface a summary that mentions "Q4 spend" or "financial plan" — even if the words don't match.
58
+
59
+ 3. **Tenant isolation enforced at the credential level.** In multi-tenant mode, each tenant gets a dedicated S3 Vectors index and STS credentials that are physically scoped to that index via IAM ABAC. A bug in application code that constructs the wrong index name is still blocked by IAM — there's no application-layer access control to misconfigure.
60
+
61
+ The result is an agent that gets smarter with every conversation, scales to millions of users, and keeps tenant data isolated by construction — not by convention.
62
+
63
+ ---
64
+
65
+ ## Repository structure
66
+
67
+ ```
68
+ ├── src/
69
+ │ └── strands_s3_vectors_memory/ # installable library
70
+ │ ├── __init__.py
71
+ │ ├── s3_vector_memory.py # S3VectorMemory + MultiTenantS3VectorMemory
72
+ │ ├── s3_vector_memory_plugin.py # S3VectorMemoryPlugin (hook-driven)
73
+ │ └── token_vending_machine.py # TVM credential manager (multi-tenant)
74
+ ├── examples/
75
+ │ ├── single_tenant_agent.py # Single-tenant runnable example
76
+ │ └── multi_tenant_agent.py # Multi-tenant runnable example (TVM + isolation demo)
77
+ ├── tests/
78
+ │ ├── unit/ # 112 tests, no AWS credentials required
79
+ │ └── integration/ # 28 tests, requires live AWS resources
80
+ ├── scripts/
81
+ │ ├── setup_tvm_role.sh # Creates the TVM IAM role with ABAC policy
82
+ │ ├── setup_cognito.sh # Cognito user pool setup
83
+ │ └── setup_agentcore.sh # AgentCore deployment setup
84
+ ├── docs/
85
+ │ └── strands-s3-vector-memory-plugin.md # Full plugin reference
86
+ ├── pyproject.toml # Package build config
87
+ └── images/
88
+ └── s3_vector_memory.png # Architecture diagram
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Requirements
94
+
95
+ - Python 3.10+
96
+ - `strands-agents >= 1.0.0`
97
+ - `boto3 >= 1.35`
98
+ - `cachetools >= 5.0` (multi-tenant only)
99
+ - AWS account with S3 Vectors access and Bedrock Nova Embeddings enabled in your region
100
+ - `amazon.nova-2-multimodal-embeddings-v1:0`
101
+ - A Claude model (e.g. `us.anthropic.claude-sonnet-4-5-20250929-v1:0`)
102
+ - AWS CLI v2 configured with credentials that can access IAM, S3 Vectors, Bedrock, and STS
103
+
104
+ ---
105
+
106
+ ## Install
107
+
108
+ ```bash
109
+ python3 -m venv .venv
110
+ source .venv/bin/activate
111
+ pip install -e .
112
+ ```
113
+
114
+ ---
115
+
116
+ ## AWS setup
117
+
118
+ ### 1. Create the S3 Vectors bucket
119
+
120
+ ```bash
121
+ export AWS_REGION=us-east-1
122
+ export S3_VECTOR_BUCKET_NAME=my-vector-memory
123
+
124
+ aws s3vectors create-vector-bucket \
125
+ --vector-bucket-name $S3_VECTOR_BUCKET_NAME \
126
+ --region $AWS_REGION
127
+ ```
128
+
129
+ ### 2. Create the TVM IAM role (multi-tenant only)
130
+
131
+ ```bash
132
+ bash scripts/setup_tvm_role.sh $S3_VECTOR_BUCKET_NAME
133
+ # Prints: export S3_VECTOR_TVM_ROLE_ARN=arn:aws:iam::<account-id>:role/...
134
+ export S3_VECTOR_TVM_ROLE_ARN=<printed-arn>
135
+ ```
136
+
137
+ ### 3. Create indexes
138
+
139
+ **Single-tenant** — one shared index named `memory`:
140
+
141
+ ```bash
142
+ aws s3vectors create-index \
143
+ --vector-bucket-name $S3_VECTOR_BUCKET_NAME \
144
+ --index-name memory \
145
+ --data-type float32 \
146
+ --dimension 1024 \
147
+ --distance-metric cosine \
148
+ --metadata-configuration '{"nonFilterableMetadataKeys":["content","conversation_id","type"]}' \
149
+ --region $AWS_REGION
150
+ ```
151
+
152
+ **Multi-tenant** — one index per tenant (repeat at onboarding time):
153
+
154
+ ```bash
155
+ for TENANT in tenant-001 tenant-002; do
156
+ aws s3vectors create-index \
157
+ --vector-bucket-name $S3_VECTOR_BUCKET_NAME \
158
+ --index-name memory-${TENANT} \
159
+ --data-type float32 \
160
+ --dimension 1024 \
161
+ --distance-metric cosine \
162
+ --metadata-configuration '{"nonFilterableMetadataKeys":["content","conversation_id","type"]}' \
163
+ --region $AWS_REGION
164
+ done
165
+ ```
166
+
167
+ ---
168
+
169
+ ## Usage
170
+
171
+ ### Single-tenant
172
+
173
+ ```python
174
+ import os
175
+ from strands import Agent
176
+ from strands.models import BedrockModel
177
+ from strands_s3_vectors_memory import S3VectorMemory, S3VectorMemoryPlugin
178
+
179
+ BASE_PROMPT = """You are a helpful assistant.
180
+
181
+ {memory_context}
182
+
183
+ Use prior context naturally in your responses."""
184
+
185
+ store = S3VectorMemory(bucket_name=os.environ["S3_VECTOR_BUCKET_NAME"])
186
+ plugin = S3VectorMemoryPlugin(store=store, base_prompt=BASE_PROMPT)
187
+ agent = Agent(
188
+ model = BedrockModel(),
189
+ tools = [plugin.memory_tool], # optional: mid-turn recall on demand
190
+ plugins = [plugin],
191
+ system_prompt = BASE_PROMPT,
192
+ )
193
+
194
+ # Turn 1 — agent responds; memory not yet stored
195
+ agent("My favourite framework is Strands Agents.", invocation_state={
196
+ "user_id": "user-001", "conversation_id": "conv-001", "end_session": False,
197
+ })
198
+
199
+ # Turn 2 — end_session=True triggers background summarization and vector store
200
+ agent("Thanks, bye.", invocation_state={
201
+ "user_id": "user-001", "conversation_id": "conv-001", "end_session": True,
202
+ })
203
+
204
+ # Next session — plugin retrieves the stored summary and injects it into the prompt
205
+ agent("What do you know about my preferences?", invocation_state={
206
+ "user_id": "user-001", "conversation_id": "conv-002", "end_session": False,
207
+ })
208
+ ```
209
+
210
+ `BASE_PROMPT` must contain a `{memory_context}` placeholder. The plugin fills it with retrieved conversation summaries on the first turn of each conversation, or replaces it with an empty string when no relevant memories are found.
211
+
212
+ ### Multi-tenant
213
+
214
+ One index per tenant. IAM credentials scoped per tenant via STS AssumeRole + TenantID session tag.
215
+
216
+ ```python
217
+ import os
218
+ from strands import Agent
219
+ from strands.models import BedrockModel
220
+ from strands_s3_vectors_memory import MultiTenantS3VectorMemory, S3VectorMemoryPlugin
221
+
222
+ BASE_PROMPT = """You are a helpful assistant.
223
+
224
+ {memory_context}
225
+
226
+ Use prior context naturally in your responses."""
227
+
228
+ store = MultiTenantS3VectorMemory(
229
+ bucket_name = os.environ["S3_VECTOR_BUCKET_NAME"],
230
+ tvm_role_arn = os.environ["S3_VECTOR_TVM_ROLE_ARN"],
231
+ )
232
+ plugin = S3VectorMemoryPlugin(store=store, base_prompt=BASE_PROMPT)
233
+ agent = Agent(
234
+ model = BedrockModel(),
235
+ tools = [plugin.memory_tool], # optional: mid-turn recall on demand
236
+ plugins = [plugin],
237
+ system_prompt = BASE_PROMPT,
238
+ )
239
+
240
+ tenant_context = {
241
+ "tenantId": "tenant-001",
242
+ "tenantName": "Acme Corp",
243
+ }
244
+
245
+ # Mid-conversation turn
246
+ agent("Our Q4 budget is $2M.", invocation_state={
247
+ "tenant_context": tenant_context,
248
+ "user_id": "user-456",
249
+ "conversation_id": "conv-001",
250
+ "end_session": False,
251
+ })
252
+
253
+ # Final turn — plugin summarizes and stores to S3 Vectors in a background thread
254
+ agent("Thanks, bye.", invocation_state={
255
+ "tenant_context": tenant_context,
256
+ "user_id": "user-456",
257
+ "conversation_id": "conv-001",
258
+ "end_session": True,
259
+ })
260
+ ```
261
+
262
+ The only difference between the two modes is the `store` class and the presence of `tenant_context` in `invocation_state`. `MultiTenantS3VectorMemory` requires `tvm_role_arn` — omitting it raises `ValueError` to prevent silent bypass of IAM ABAC isolation.
263
+
264
+ ### `invocation_state` keys
265
+
266
+ | Key | Type | Required | Description |
267
+ |-----|------|----------|-------------|
268
+ | `user_id` | `str` | Yes | User identifier — used as metadata filter on vector operations |
269
+ | `conversation_id` | `str` | Yes | Unique conversation ID — scopes buffer and summary key |
270
+ | `end_session` | `bool` | No (default `False`) | If `True`, summarize and store conversation after response (non-blocking) |
271
+ | `tenant_context` | `dict` | Multi-tenant only | Must contain `tenantId` |
272
+
273
+ ### `memory_tool` — mid-turn recall on demand
274
+
275
+ The plugin exposes a `memory_tool` property that returns a Strands `@tool`. When wired to the agent, the LLM can call it mid-conversation to retrieve specific memories it discovers it needs during reasoning — without starting a new session.
276
+
277
+ ```python
278
+ agent = Agent(
279
+ model = BedrockModel(),
280
+ tools = [plugin.memory_tool], # LLM calls this when it needs to recall something
281
+ plugins = [plugin], # handles auto-inject + end_session store
282
+ system_prompt = BASE_PROMPT,
283
+ )
284
+ ```
285
+
286
+ The tool is **retrieve-only**. The LLM provides a natural language `query`; identity (`user_id`, `tenant_context`) is read automatically from the plugin's context — the LLM never sees or handles credentials.
287
+
288
+ **When to use it:** the automatic `before_invocation` injection handles broad contextual priming on the first turn. The `memory_tool` handles *specific, targeted recall* the agent discovers it needs mid-reasoning — for example, a topic pivot mid-conversation, a temporally distant memory with different keywords, or a fact the LLM needs to complete a chain-of-thought.
289
+
290
+ ---
291
+
292
+ See the [plugin reference](docs/strands-s3-vector-memory-plugin.md#testing) for full details on running unit and integration tests, required env vars, and debug logging.
293
+
294
+ ---
295
+
296
+ ## Run the examples
297
+
298
+ The examples require the indexes to exist before running. The integration tests create and delete indexes automatically, but the examples expect them to be present persistently.
299
+
300
+ ### Single-tenant
301
+
302
+ **Step 1 — create the index** (once, before first run):
303
+
304
+ ```bash
305
+ aws s3vectors create-index \
306
+ --vector-bucket-name $S3_VECTOR_BUCKET_NAME \
307
+ --index-name memory \
308
+ --data-type float32 --dimension 1024 --distance-metric cosine \
309
+ --metadata-configuration '{"nonFilterableMetadataKeys":["content","conversation_id","type"]}' \
310
+ --region $AWS_REGION
311
+ ```
312
+
313
+ **Step 2 — run:**
314
+
315
+ ```bash
316
+ source .venv/bin/activate
317
+ export S3_VECTOR_BUCKET_NAME=my-vector-memory
318
+ export AWS_REGION=us-east-1
319
+
320
+ cd examples
321
+ python3 single_tenant_agent.py
322
+ ```
323
+
324
+ **Expected output:**
325
+
326
+ ```
327
+ ============================================================
328
+ SESSION 1 — storing a fact
329
+ ============================================================
330
+
331
+ USER: My favourite framework is Strands Agents.
332
+ AGENT: That's great! Strands Agents is a nice framework to work with...
333
+
334
+ USER: What framework did I mention?
335
+ AGENT: You mentioned Strands Agents as your favourite framework.
336
+ [end_session=True — summarizing in background]
337
+
338
+ [waiting 5s for background summary store to complete...]
339
+
340
+ ============================================================
341
+ SESSION 2 — memory injected automatically on first turn
342
+ ============================================================
343
+
344
+ USER: What do you know about my preferences?
345
+ AGENT: Based on our previous conversations, I know that your favorite
346
+ framework is Strands Agents.
347
+
348
+ ============================================================
349
+ SESSION 3 — memory_tool: mid-turn recall on demand
350
+ The agent uses the memory_tool when it needs to recall
351
+ something specific mid-conversation.
352
+ ============================================================
353
+
354
+ USER: I'm evaluating some new tools. By the way, remind me — what
355
+ framework did I mention I liked in a previous session?
356
+ AGENT: You mentioned that Strands Agents is your favorite framework.
357
+ Good luck with evaluating the new tools!
358
+ ```
359
+
360
+ ### Multi-tenant (with tenant isolation demo)
361
+
362
+ **Step 1 — create indexes for both tenants** (once, before first run):
363
+
364
+ ```bash
365
+ for TENANT in tenant-001 tenant-002; do
366
+ aws s3vectors create-index \
367
+ --vector-bucket-name $S3_VECTOR_BUCKET_NAME \
368
+ --index-name memory-${TENANT} \
369
+ --data-type float32 --dimension 1024 --distance-metric cosine \
370
+ --metadata-configuration '{"nonFilterableMetadataKeys":["content","conversation_id","type"]}' \
371
+ --region $AWS_REGION
372
+ done
373
+ ```
374
+
375
+ **Step 2 — run:**
376
+
377
+ ```bash
378
+ source .venv/bin/activate
379
+ export S3_VECTOR_BUCKET_NAME=my-vector-memory
380
+ export S3_VECTOR_TVM_ROLE_ARN=arn:aws:iam::<account-id>:role/<tvm-role-name>
381
+ export AWS_REGION=us-east-1
382
+
383
+ cd examples
384
+ python3 multi_tenant_agent.py
385
+ ```
386
+
387
+ **Expected output:**
388
+
389
+ ```
390
+ ============================================================
391
+ SESSION 1 — tenant=tenant-001 storing a fact
392
+ ============================================================
393
+
394
+ USER: Our Q4 budget is $2M and it is confidential.
395
+ AGENT: I understand and acknowledge that your Q4 budget is $2M and
396
+ that this information is confidential...
397
+
398
+ USER: Got it, thanks. [end_session=True]
399
+ AGENT: You're welcome! Feel free to reach out anytime...
400
+
401
+ [waiting 5s for background summary store to complete...]
402
+
403
+ ============================================================
404
+ SESSION 2 — tenant=tenant-001 memory should be recalled
405
+ ============================================================
406
+
407
+ USER: What did I tell you about our budget?
408
+ AGENT: You shared that your Q4 budget is $2M, and you indicated that
409
+ this information is confidential...
410
+
411
+ ============================================================
412
+ SESSION 3 — tenant=tenant-002 isolation check
413
+ tenant-002 asks the same question about the budget.
414
+ Expected: agent has NO memory — cannot see tenant-001's data.
415
+ ============================================================
416
+
417
+ USER (tenant-002): What did I tell you about our budget?
418
+ AGENT: I don't have any record of you telling me about your budget
419
+ in our previous conversations...
420
+
421
+ 👆 Review the response above — tenant-002 should have no knowledge of
422
+ tenant-001's Q4 budget. If the response mentions '$2M', isolation has failed.
423
+
424
+ ============================================================
425
+ SESSION 4 — tenant=tenant-001 memory_tool: mid-turn recall on demand
426
+ The agent uses the memory_tool when it discovers mid-reasoning
427
+ that it needs a specific fact from a previous session.
428
+ ============================================================
429
+
430
+ USER: We're planning Q1 now. Can you remind me what our Q4 budget
431
+ was and whether there were any constraints I mentioned?
432
+ AGENT: Based on our previous conversation, your Q4 budget was $2M.
433
+ You marked this as confidential information...
434
+ ```
435
+
436
+ Session 3 demonstrates application-level isolation — `tenant-002` has its own empty index and receives no memory context. The TVM credentials for `tenant-002` are also physically scoped to `memory-tenant-002` by IAM ABAC, so even a direct API call to `memory-tenant-001` would be denied with `AccessDeniedException`.
437
+
438
+ ---
439
+
440
+ ## Clean up
441
+
442
+ ```bash
443
+ # Delete indexes
444
+ for INDEX in memory memory-tenant-001 memory-tenant-002; do
445
+ aws s3vectors delete-index \
446
+ --vector-bucket-name $S3_VECTOR_BUCKET_NAME \
447
+ --index-name $INDEX \
448
+ --region $AWS_REGION
449
+ done
450
+
451
+ # Delete the bucket
452
+ aws s3vectors delete-vector-bucket \
453
+ --vector-bucket-name $S3_VECTOR_BUCKET_NAME \
454
+ --region $AWS_REGION
455
+
456
+ # Delete the TVM IAM role
457
+ aws iam delete-role-policy \
458
+ --role-name workshop-module3-lab1-s3vectors-tvm-role \
459
+ --policy-name S3VectorsTenantPolicy
460
+
461
+ aws iam delete-role \
462
+ --role-name workshop-module3-lab1-s3vectors-tvm-role
463
+ ```
464
+
465
+ ---
466
+
467
+ ## Further reading
468
+
469
+ For a deep dive into how the plugin works — lifecycle hooks, tenant isolation, conversation buffer, TVM credential caching, and design decisions — see the [plugin reference](docs/strands-s3-vector-memory-plugin.md).
470
+
471
+ - [Amazon S3 Vectors documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors.html)
472
+ - [Strands Agents Plugins](https://strandsagents.com/docs/user-guide/concepts/plugins/)
473
+ - [Token Vending Machine pattern](https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/implement-saas-tenant-isolation-for-amazon-s3-by-using-an-aws-lambda-token-vending-machine.html)
474
+ - [AWS STS session tags](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html)