Skip to content

Commit 1053076

Browse files
authored
fix(memory): await async state transitions so samples stop reporting empty results as success (#1848)
* fix(memory): wait for UpdateMemory to settle in both getting-started quickstarts Both quickstarts fail at teardown when run exactly as 00-getting-started/README.md documents: botocore.errorfactory.ValidationException: An error occurred (ValidationException) when calling the DeleteMemory operation: Validation failed during DeleteMemory: Memory is in transitional state UPDATING. Cannot delete memory. 04-quickstart-boto3.py exits 1 after ~214s. Both leave a billable memory resource behind, since the delete never succeeds. Root cause: one missing wait, three symptoms. Both scripts add a semantic strategy via UpdateMemory and then continue immediately. UpdateMemory is asynchronous. A post-mortem of the leaked resource shows the update had not been applied at all: { "id": "QuickstartMemory-49xcFY89hV", "status": "ACTIVE", "strategies": null } strategies is null even though the script explicitly added one. From that single omission: 1. The following sleep waits for an extraction that cannot start, because the strategy is not active yet. 2. RetrieveMemoryRecords returns an empty list, so the final print loop outputs nothing and the quickstart's headline lesson silently produces no result. 3. DeleteMemory fires while the resource is still UPDATING, raises, and leaks. Symptom 3 is the loud one. Symptom 2 is worse for a first-time reader: the getting-started sample appears to run, prints no retrieved memory, and gives no indication why. Fixes, one per surface: 04-quickstart-boto3.py — poll GetMemory until ACTIVE after UpdateMemory. This is the same loop the file already uses after CreateMemory (lines 76-85), just applied to the second state transition as well. The subsequent extraction sleep is also raised 60s -> 90s to match measured latency (see the companion commit). 05-quickstart-agentcore-sdk.py — switch update_memory_strategies() to update_memory_strategies_and_wait(). The SDK already ships this variant with an identical signature plus max_wait/poll_interval, so no custom polling is needed; it is a one-word change. Verified against a live account (us-east-1, bedrock-agentcore 1.19.0). Both scripts now run to completion with no traceback and delete their own resources. Leaked resource count across a full run of the area went from 2 to 0. The failure was also hit independently by a second tester a day before this investigation, on an unmodified checkout. * fix(memory): poll for extraction instead of a fixed sleep in long-term memory samples Five long-term-memory samples retrieved zero records and still exited 0. Each writes events, sleeps a hard-coded number of seconds, retrieves exactly once, prints whatever came back, and tears down. When extraction has not finished, the result is an empty list reported as success. Two of the five do not even print a count, so the failure is invisible: [boto3] Preferences in /users/user-alex/preferences/: <- nothing follows [boto3] Summary records in /sessions/.../summary/: <- nothing follows 04-namespaces prints its counts, which is how the problem was first spotted: [boto3] Exact — /facts/user1/ (0): [boto3] Tenant — /facts/tenantA/* (0): [boto3] All — /facts/* (0): Three empty result sets, exit code 0, no error. The lesson being taught — that records route to the right namespace — produces no evidence either way, and an automated run-through scores it as a pass. The waits were already suspect in the source. Each file defines two constants and gives the shorter one to the boto3 surface, which is the surface the README documents as the default: EXTRACTION_WAIT_SECONDS = 60 SESSION_EXTRACTION_WAIT_SECONDS = 90 # semantic extraction surfaces ~60-90s; extra margin The comment states real latency is ~60-90s, then assigns the bottom of that range to one surface and the margin to the other. Across a full parallel run the correlation was total: every 60s boto3 run returned nothing, every 90s sdk run on the same script returned records. Raising 60 -> 90 is not sufficient. Measured against a live account by polling RetrieveMemoryRecords every 10s, user-preference extraction produced its first record at 93 seconds — three seconds past that fix. Any fixed sleep is a guess that eventually loses, so this replaces the mechanism rather than the number: - retrieve in a loop with a 10s interval, breaking as soon as records appear - EXTRACTION_WAIT_SECONDS becomes a polling budget (180s) rather than a blind sleep - print an explicit message when the budget expires with nothing found, so an empty result can no longer be mistaken for a successful demonstration Fast runs get faster (they break out as soon as records land); slow runs still succeed. Files, all on the boto3 surface: 02-long-term-memory/standard-usage.py 02-long-term-memory/01-built-in-strategies/user-preference.py 02-long-term-memory/01-built-in-strategies/summary.py 02-long-term-memory/04-namespaces/namespaces-and-organization.py 02-long-term-memory/05-retrieval/retrieve-records-and-citations.py The last two already passed with a fixed 90s wait but are converted as well, so they cannot regress silently the next time extraction runs slow. Verified against a live account (us-east-1, boto3 1.43.58, bedrock-agentcore 1.19.0), all five run in parallel, before and after: standard-usage 0 -> 2 records user-preference 0 -> 1 preference record summary 0 -> 1 summary record namespaces-and-organization 0/0/0 -> 1/4/7 across the three query scopes retrieve-records-and-citations 0 -> 3 records + ListMemoryRecords + GetMemoryRecord namespaces-and-organization is the clearest result: it now demonstrates the actual lesson, showing records resolved under /facts/user1/, /facts/tenantA/user1/ and /facts/tenantA/user2/ instead of three zeros. * fix(memory): poll for extraction in 02-strategy-overrides, both surfaces strategies-with-overrides.py retrieved zero records on both the boto3 and sdk surfaces and exited 0 in each case: [boto3] Medical facts (0): [boto3] The Godfather mention should NOT appear — override suppresses it. rc=0 [sdk] Medical facts (0): rc=0 Same root cause as the five samples in the previous commit — a single blind time.sleep() before one retrieval attempt — but with a sharper consequence, because this lesson asserts a fact is *absent*. The sample deliberately feeds in one non-medical line ("my favourite movie is The Godfather") and teaches that the semanticOverride prompt suppresses it. An empty result set satisfies that claim vacuously: the reader sees the "should NOT appear" message printed directly beneath an empty list, and cannot tell whether the override worked or whether nothing was extracted at all. A failed run is indistinguishable from a successful demonstration. The margin here was the thinnest in the folder. Measured against a live account by polling RetrieveMemoryRecords every 10s, override extraction produced its first record at 73 seconds: 0s: 0 records 31s: 0 records 62s: 0 records 73s: FIRST RECORD (2 records) The script waited 75 — two seconds of headroom. Under parallel load that is reliably not enough, which is why both surfaces came back empty. This is also the most model-dependent path in the tree: overrides invoke a caller-specified Bedrock model for both extraction and consolidation, so its latency moves with the chosen model, and no fixed sleep can be correct for every MODEL_ID. Changes, applied to both surfaces: - retrieve in a loop with a 10s interval, breaking as soon as records appear - EXTRACTION_WAIT_SECONDS becomes a 180s polling budget rather than a blind sleep - the "should NOT appear" line now prints only when records were actually retrieved; an empty result prints an explicit note that suppression cannot be demonstrated from that run That last point is the substantive difference from the previous commit. Elsewhere an empty retrieval is merely uninformative; here it would actively assert something the run did not show. Verified against a live account (us-east-1), both surfaces run in parallel: boto3 0 -> 2 records ("mother had breast cancer at 52", "has type 2 diabetes") sdk 0 -> 1 record ("takes metformin twice daily for type 2 diabetes") The Godfather line is correctly absent from both, so the override is now demonstrably doing its job rather than being credited for an empty result. MODEL_ID is deliberately untouched here; the retired-model-id fix for this file is in a separate change. * fix(memory): tighten the summary.py polling budget to 100s The polling budget in summary.py was 180s, which was an arbitrary ceiling rather than a measured one, and its comment cited a ~93s latency that was measured on user-preference.py, not on this sample. Summary uses summaryMemoryStrategy and does consolidation rather than extraction, so the borrowed figure did not describe it. Measured directly against a live account, three parallel trials, timing from the last CreateEvent to the first retrievable summary record: trial A: 87s trial B: 104s trial C: 75s The comment now cites that 75-104s range instead of the borrowed number. Because the loop breaks as soon as records appear, the budget only affects slow runs: a fast run returns at 75s under any ceiling. 100s covers the median case and keeps the sample from idling for three minutes when consolidation has clearly stalled. Note for reviewers: trial B exceeded this budget at 104s. On a run that slow the script prints "No records after 100s - consolidation may still be running" and shows no summary, rather than hanging. That message exists because of this PR's other changes, so a slow run now says so explicitly instead of printing an empty result as if it were the answer. If the preference is that no run should ever come up short, 120s covers all three measured trials. Passes ruff check and ruff format --check per .github/workflows/python-lint.yml. * fix(memory): tighten the remaining polling budgets to 100s Applies the same change already made to summary.py across the other five samples this PR touches, so the whole set uses one budget: 02-long-term-memory/standard-usage.py 02-long-term-memory/01-built-in-strategies/user-preference.py 02-long-term-memory/02-strategy-overrides/strategies-with-overrides.py 02-long-term-memory/04-namespaces/namespaces-and-organization.py 02-long-term-memory/05-retrieval/retrieve-records-and-citations.py The previous 180s was an arbitrary ceiling. Because each loop breaks as soon as records appear, the budget only decides what happens on a slow run, so a lower ceiling costs nothing on a normal one and stops the sample idling for three minutes when extraction has clearly stalled. Verified live in us-east-1, all five run against a live account with the new budget in place: standard-usage.py 4 records 04-namespaces 2 / 6 / 10 across the three scopes 01-built-in-strategies/user-preference 3 preference records 05-retrieval 4 records + ListMemoryRecords 02-strategy-overrides 1 medical fact, Godfather line absent Every one returned records inside the 100s budget, and none printed the "may still be running" message this PR added. Note for reviewers: the measured latencies these budgets cover are 73s for override extraction and ~93s for semantic and preference extraction, so 100s is a margin of roughly 7s over the slowest measurement. Separate measurement of summary consolidation, done when its budget was set in the previous commit, produced 75s / 87s / 104s across three trials, so a run at the slow end of that spread can exceed 100s. When that happens the sample now prints an explicit message rather than presenting an empty result as the answer, which is the behaviour this PR exists to add. If reviewers prefer that no run ever comes up short, 120s covers every latency measured here. Passes ruff check and ruff format --check per .github/workflows/python-lint.yml.
1 parent c36db47 commit 1053076

8 files changed

Lines changed: 127 additions & 61 deletions

File tree

01-features/04-manage-context-of-your-agent/memory/00-getting-started/04-quickstart-boto3.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,18 @@
137137
},
138138
)
139139

140-
# Extraction is asynchronous — give it ~60s before retrieving.
141-
time.sleep(60)
140+
# UpdateMemory is asynchronous. Wait for ACTIVE before relying on the new strategy —
141+
# extraction only runs once it is applied, and DeleteMemory below is rejected while
142+
# the resource is still UPDATING.
143+
deadline = time.time() + 300
144+
while time.time() < deadline:
145+
status = control.get_memory(memoryId=memory_id)["memory"]["status"]
146+
if status == "ACTIVE":
147+
break
148+
time.sleep(5)
149+
150+
# Extraction is asynchronous — give it ~90s before retrieving.
151+
time.sleep(90)
142152

143153
hits = data.retrieve_memory_records(
144154
memoryId=memory_id,

01-features/04-manage-context-of-your-agent/memory/00-getting-started/05-quickstart-agentcore-sdk.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
for msg in turn:
3636
print(msg["role"], "→", msg["content"]["text"])
3737

38-
client.update_memory_strategies(
38+
client.update_memory_strategies_and_wait(
3939
memory_id=memory_id,
4040
add_strategies=[
4141
{

01-features/04-manage-context-of-your-agent/memory/02-long-term-memory/01-built-in-strategies/summary.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
REGION = os.getenv("AWS_REGION", "us-east-1")
3636
ACTOR_ID = "user-alex"
3737
SESSION_ID = f"sess-{int(time.time())}"
38-
EXTRACTION_WAIT_SECONDS = 75
38+
EXTRACTION_WAIT_SECONDS = 100 # polling budget; summary consolidation measured 75-104s
3939
# Summary consolidation is semantic-class; the high-level sdk run waits
4040
# 90s (with margin) — consolidation surfaced ~64s in semantic-class testing.
4141
SESSION_EXTRACTION_WAIT_SECONDS = 90
@@ -89,15 +89,22 @@ def run_with_boto3(cleanup: bool = False) -> None:
8989
eventTimestamp=datetime.now(timezone.utc),
9090
payload=[{"conversational": {"role": role, "content": {"text": text}}}],
9191
)
92-
print(f"[boto3] Waiting {EXTRACTION_WAIT_SECONDS}s for summary consolidation...")
93-
time.sleep(EXTRACTION_WAIT_SECONDS)
94-
92+
# Consolidation is asynchronous and its latency varies — poll instead of sleeping a
93+
# fixed amount, so a slow run still shows records instead of printing an empty list.
94+
print(f"[boto3] Polling up to {EXTRACTION_WAIT_SECONDS}s for summary consolidation...")
9595
namespace = NAMESPACE_TEMPLATE.format(sessionId=SESSION_ID)
96-
hits = data.retrieve_memory_records(
97-
memoryId=memory_id,
98-
namespace=namespace,
99-
searchCriteria={"searchQuery": "trip plan", "topK": 5},
100-
)["memoryRecordSummaries"]
96+
deadline = time.time() + EXTRACTION_WAIT_SECONDS
97+
while True:
98+
hits = data.retrieve_memory_records(
99+
memoryId=memory_id,
100+
namespace=namespace,
101+
searchCriteria={"searchQuery": "trip plan", "topK": 5},
102+
)["memoryRecordSummaries"]
103+
if hits or time.time() >= deadline:
104+
break
105+
time.sleep(10)
106+
if not hits:
107+
print(f"[boto3] No records after {EXTRACTION_WAIT_SECONDS}s — consolidation may still be running.")
101108
print(f"\n[boto3] Summary records in {namespace}:")
102109
for h in hits:
103110
print(f" - {h['content']['text']}")

01-features/04-manage-context-of-your-agent/memory/02-long-term-memory/01-built-in-strategies/user-preference.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
REGION = os.getenv("AWS_REGION", "us-east-1")
3535
ACTOR_ID = "user-alex"
3636
SESSION_ID = f"sess-{int(time.time())}"
37-
EXTRACTION_WAIT_SECONDS = 60
37+
EXTRACTION_WAIT_SECONDS = 100 # polling budget; preference extraction measured ~93s
3838
# Preference extraction is semantic-class; it surfaced ~64s in testing, so the
3939
# high-level sdk run waits 90s (with margin) rather than the 60s above.
4040
SESSION_EXTRACTION_WAIT_SECONDS = 90
@@ -86,15 +86,22 @@ def run_with_boto3(cleanup: bool = False) -> None:
8686
eventTimestamp=datetime.now(timezone.utc),
8787
payload=[{"conversational": {"role": role, "content": {"text": text}}}],
8888
)
89-
print(f"[boto3] Waiting {EXTRACTION_WAIT_SECONDS}s for extraction...")
90-
time.sleep(EXTRACTION_WAIT_SECONDS)
91-
89+
# Extraction is asynchronous and its latency varies — poll instead of sleeping a
90+
# fixed amount, so a slow run still shows records instead of printing an empty list.
91+
print(f"[boto3] Polling up to {EXTRACTION_WAIT_SECONDS}s for extraction...")
9292
namespace = NAMESPACE_TEMPLATE.format(actorId=ACTOR_ID)
93-
hits = data.retrieve_memory_records(
94-
memoryId=memory_id,
95-
namespace=namespace,
96-
searchCriteria={"searchQuery": "user's preferences", "topK": 10},
97-
)["memoryRecordSummaries"]
93+
deadline = time.time() + EXTRACTION_WAIT_SECONDS
94+
while True:
95+
hits = data.retrieve_memory_records(
96+
memoryId=memory_id,
97+
namespace=namespace,
98+
searchCriteria={"searchQuery": "user's preferences", "topK": 10},
99+
)["memoryRecordSummaries"]
100+
if hits or time.time() >= deadline:
101+
break
102+
time.sleep(10)
103+
if not hits:
104+
print(f"[boto3] No records after {EXTRACTION_WAIT_SECONDS}s — extraction may still be running.")
98105
print(f"\n[boto3] Preferences in {namespace}:")
99106
for h in hits:
100107
print(f" - {h['content']['text']}")

01-features/04-manage-context-of-your-agent/memory/02-long-term-memory/02-strategy-overrides/strategies-with-overrides.py

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
MODEL_ID = os.getenv("OVERRIDE_MODEL_ID", "global.anthropic.claude-opus-4-6-v1")
3636
ACTOR_ID = "user-alex"
3737
SESSION_ID = f"sess-{int(time.time())}"
38-
EXTRACTION_WAIT_SECONDS = 75
38+
EXTRACTION_WAIT_SECONDS = 100 # polling budget; override extraction measured ~73s
3939
NAMESPACE_TEMPLATE = "/users/{actorId}/medical-facts/"
4040

4141
EXTRACTION_ADDENDUM = (
@@ -110,19 +110,31 @@ def run_with_boto3(cleanup: bool = False) -> None:
110110
eventTimestamp=datetime.now(timezone.utc),
111111
payload=[{"conversational": {"role": role, "content": {"text": text}}}],
112112
)
113-
print(f"[boto3] Waiting {EXTRACTION_WAIT_SECONDS}s for extraction...")
114-
time.sleep(EXTRACTION_WAIT_SECONDS)
115-
113+
# Override extraction is asynchronous and invokes MODEL_ID for both extraction and
114+
# consolidation — poll instead of sleeping a fixed amount. This lesson asserts a
115+
# fact is *absent*, so an empty result would satisfy it vacuously.
116+
print(f"[boto3] Polling up to {EXTRACTION_WAIT_SECONDS}s for extraction...")
116117
namespace = NAMESPACE_TEMPLATE.format(actorId=ACTOR_ID)
117-
hits = data.retrieve_memory_records(
118-
memoryId=memory_id,
119-
namespace=namespace,
120-
searchCriteria={"searchQuery": "user's medical history", "topK": 10},
121-
)["memoryRecordSummaries"]
118+
deadline = time.time() + EXTRACTION_WAIT_SECONDS
119+
while True:
120+
hits = data.retrieve_memory_records(
121+
memoryId=memory_id,
122+
namespace=namespace,
123+
searchCriteria={"searchQuery": "user's medical history", "topK": 10},
124+
)["memoryRecordSummaries"]
125+
if hits or time.time() >= deadline:
126+
break
127+
time.sleep(10)
122128
print(f"\n[boto3] Medical facts ({len(hits)}):")
123129
for h in hits:
124130
print(f" - {h['content']['text']}")
125-
print("\n[boto3] The Godfather mention should NOT appear — override suppresses it.")
131+
if hits:
132+
print("\n[boto3] The Godfather mention should NOT appear — override suppresses it.")
133+
else:
134+
print(
135+
f"\n[boto3] No records after {EXTRACTION_WAIT_SECONDS}s — extraction may still be "
136+
"running, so suppression cannot be demonstrated from this run."
137+
)
126138

127139
if cleanup:
128140
control.delete_memory(memoryId=memory_id, clientToken=str(uuid.uuid4()))
@@ -155,19 +167,28 @@ def run_with_sdk(cleanup: bool = False) -> None:
155167
session_id=SESSION_ID,
156168
messages=[(text, role) for role, text in TURNS],
157169
)
158-
print(f"[sdk] Waiting {EXTRACTION_WAIT_SECONDS}s for extraction...")
159-
time.sleep(EXTRACTION_WAIT_SECONDS)
160-
170+
# Same polling rationale as the boto3 path above.
171+
print(f"[sdk] Polling up to {EXTRACTION_WAIT_SECONDS}s for extraction...")
161172
namespace = NAMESPACE_TEMPLATE.format(actorId=ACTOR_ID)
162-
hits = client.retrieve_memories(
163-
memory_id=memory_id,
164-
namespace=namespace,
165-
query="user's medical history",
166-
top_k=10,
167-
)
173+
deadline = time.time() + EXTRACTION_WAIT_SECONDS
174+
while True:
175+
hits = client.retrieve_memories(
176+
memory_id=memory_id,
177+
namespace=namespace,
178+
query="user's medical history",
179+
top_k=10,
180+
)
181+
if hits or time.time() >= deadline:
182+
break
183+
time.sleep(10)
168184
print(f"\n[sdk] Medical facts ({len(hits)}):")
169185
for h in hits:
170186
print(f" - {h['content']['text']}")
187+
if not hits:
188+
print(
189+
f"\n[sdk] No records after {EXTRACTION_WAIT_SECONDS}s — extraction may still be "
190+
"running, so suppression cannot be demonstrated from this run."
191+
)
171192

172193
if cleanup:
173194
client.delete_memory_and_wait(memory_id=memory_id)

01-features/04-manage-context-of-your-agent/memory/02-long-term-memory/04-namespaces/namespaces-and-organization.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from datetime import datetime, timezone
3232

3333
REGION = os.getenv("AWS_REGION", "us-east-1")
34-
EXTRACTION_WAIT_SECONDS = 60
34+
EXTRACTION_WAIT_SECONDS = 100 # polling budget; semantic extraction measured ~93s
3535
SDK_EXTRACTION_WAIT_SECONDS = 90 # semantic extraction surfaces ~60-90s; extra margin
3636
FACTS_TEMPLATE = "/facts/{actorId}/"
3737

@@ -107,8 +107,19 @@ def run_with_boto3(cleanup: bool = False) -> None:
107107
},
108108
],
109109
)
110-
print(f"[boto3] Waiting {EXTRACTION_WAIT_SECONDS}s for extraction...")
111-
time.sleep(EXTRACTION_WAIT_SECONDS)
110+
# Extraction is asynchronous and its latency varies — poll on the broadest query
111+
# instead of sleeping a fixed amount, so a slow run still shows records.
112+
print(f"[boto3] Polling up to {EXTRACTION_WAIT_SECONDS}s for extraction...")
113+
deadline = time.time() + EXTRACTION_WAIT_SECONDS
114+
_, probe_query, probe_scope = QUERIES[-1]
115+
while time.time() < deadline:
116+
if data.retrieve_memory_records(
117+
memoryId=memory_id,
118+
searchCriteria={"searchQuery": probe_query, "topK": 20},
119+
**probe_scope,
120+
)["memoryRecordSummaries"]:
121+
break
122+
time.sleep(10)
112123

113124
for label, query, scope in QUERIES:
114125
hits = data.retrieve_memory_records(

01-features/04-manage-context-of-your-agent/memory/02-long-term-memory/05-retrieval/retrieve-records-and-citations.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
REGION = os.getenv("AWS_REGION", "us-east-1")
3737
ACTOR_ID = "user-alex"
3838
SESSION_ID = f"sess-{int(time.time())}"
39-
EXTRACTION_WAIT_SECONDS = 60
39+
EXTRACTION_WAIT_SECONDS = 100 # polling budget; semantic extraction measured ~93s
4040
SESSION_EXTRACTION_WAIT_SECONDS = 90 # semantic extraction surfaces ~60-90s; extra margin
4141
NAMESPACE_TEMPLATE = "/users/{actorId}/facts/"
4242

@@ -85,15 +85,20 @@ def run_with_boto3(cleanup: bool = False) -> None:
8585
eventTimestamp=datetime.now(timezone.utc),
8686
payload=[{"conversational": {"role": role, "content": {"text": text}}}],
8787
)
88-
print(f"[boto3] Waiting {EXTRACTION_WAIT_SECONDS}s for extraction...")
89-
time.sleep(EXTRACTION_WAIT_SECONDS)
90-
88+
# Extraction is asynchronous and its latency varies — poll instead of sleeping a
89+
# fixed amount, so a slow run still shows records instead of printing an empty list.
90+
print(f"[boto3] Polling up to {EXTRACTION_WAIT_SECONDS}s for extraction...")
9191
namespace = NAMESPACE_TEMPLATE.format(actorId=ACTOR_ID)
92-
semantic = data.retrieve_memory_records(
93-
memoryId=memory_id,
94-
namespace=namespace,
95-
searchCriteria={"searchQuery": "dietary restrictions", "topK": 5},
96-
)["memoryRecordSummaries"]
92+
deadline = time.time() + EXTRACTION_WAIT_SECONDS
93+
while True:
94+
semantic = data.retrieve_memory_records(
95+
memoryId=memory_id,
96+
namespace=namespace,
97+
searchCriteria={"searchQuery": "dietary restrictions", "topK": 5},
98+
)["memoryRecordSummaries"]
99+
if semantic or time.time() >= deadline:
100+
break
101+
time.sleep(10)
97102
print(f"\n[boto3] Semantic search 'dietary restrictions' ({len(semantic)}):")
98103
for h in semantic:
99104
print(f" - score={h.get('score'):.3f} | {h['content']['text']}")

01-features/04-manage-context-of-your-agent/memory/02-long-term-memory/standard-usage.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
REGION = os.getenv("AWS_REGION", "us-east-1")
3434
ACTOR_ID = "user-42"
3535
SESSION_ID = f"sess-{int(time.time())}"
36-
EXTRACTION_WAIT_SECONDS = 60
36+
EXTRACTION_WAIT_SECONDS = 100 # polling budget; semantic extraction measured ~93s
3737
SESSION_EXTRACTION_WAIT_SECONDS = 90 # semantic extraction surfaces ~60-90s; extra margin
3838
NAMESPACE_TEMPLATE = "/users/{actorId}/facts/"
3939

@@ -79,15 +79,20 @@ def run_with_boto3(cleanup: bool = False) -> None:
7979
payload=[{"conversational": {"role": role, "content": {"text": text}}}],
8080
)
8181

82-
print(f"[boto3] Waiting {EXTRACTION_WAIT_SECONDS}s for extraction...")
83-
time.sleep(EXTRACTION_WAIT_SECONDS)
84-
82+
# Extraction is asynchronous and its latency varies — poll instead of sleeping a
83+
# fixed amount, so a slow run still shows records instead of printing an empty list.
84+
print(f"[boto3] Polling up to {EXTRACTION_WAIT_SECONDS}s for extraction...")
8585
namespace = NAMESPACE_TEMPLATE.format(actorId=ACTOR_ID)
86-
hits = data.retrieve_memory_records(
87-
memoryId=memory_id,
88-
namespace=namespace,
89-
searchCriteria={"searchQuery": "Alex's preferences and constraints?", "topK": 5},
90-
)["memoryRecordSummaries"]
86+
deadline = time.time() + EXTRACTION_WAIT_SECONDS
87+
while True:
88+
hits = data.retrieve_memory_records(
89+
memoryId=memory_id,
90+
namespace=namespace,
91+
searchCriteria={"searchQuery": "Alex's preferences and constraints?", "topK": 5},
92+
)["memoryRecordSummaries"]
93+
if hits or time.time() >= deadline:
94+
break
95+
time.sleep(10)
9196
print(f"[boto3] Retrieved {len(hits)} records from {namespace}")
9297
for h in hits:
9398
print(f" - {h['content']['text']}")

0 commit comments

Comments
 (0)