Enable server-side metadata filtering on AgentCore Memory records and drop summary strategy - #4
Open
tverney wants to merge 1 commit into
Open
Enable server-side metadata filtering on AgentCore Memory records and drop summary strategy#4tverney wants to merge 1 commit into
tverney wants to merge 1 commit into
Conversation
Garden sections were stored with custom metadata that could not be used to filter
retrieval, so "which beds have basil?" fell back to similarity search over prose.
This makes those dimensions queryable and corrects several claims in the code and
docs that turned out to be wrong.
Template (AgentCoreMemory):
- Declare IndexedKeys: type (STRING), section (STRING), plants (STRINGLIST). Only
indexed keys may appear in a metadataFilters expression; filtering an unindexed
key raises ValidationException. Kept to three because indexed keys are
additive-only, capped at 10, and cannot be removed once added. photo_count stays
unindexed enrichment.
- Give each strategy a MemoryRecordSchema.MetadataSchema. Definition +
LlmExtractionInstruction are the instructions handed to the extraction model, and
Validation.AllowedValues/MaxItems constrain output so filter values stay
consistent. This is the supported way to guide extraction, rather than an
AppendToPrompt override.
- Remove SummaryMemoryStrategy. It produced nothing over weeks of real use (94
records in the live namespace, all semantic/user-preference; its old episodic
namespace held zero), it restated in looser prose what semantic extraction
already stores densely, and it accumulated one record per session competing for
the capped retrieval budget. The absence is documented, including the constraint
that a re-added summarization namespace must end in {sessionId}.
Runtime (server.py):
- retrieve() pushes indexed keys down as metadataFilters, which the service applies
BEFORE the vector search, so filters narrow the candidate set rather than trimming
top-K results. build_metadata_filters splits indexed from unindexed keys and uses
CONTAINS for the plants STRINGLIST; unindexed keys are filtered in-process.
- retrieve() queries namespacePath, not namespace. Verified against the service:
namespace matches only the EXACT value given, so anything nested deeper is
silently omitted (the API docs describing it as a prefix are misleading).
- persist() forwards CreateEvent metadata, and a captioned album attaches
type/section at event time so extraction propagates the same dimension onto
derived records instead of re-inferring it. Metadata is therefore no longer added
only through the batch API. CreateEvent metadata is stringValue-only, which is why
list-valued plants still travels the direct-write path.
- Add temporal scoping via build_created_after_filter / retrieve(created_after=...)
on the reserved x-amz-agentcore-memory-createdAt field, which is filterable
without consuming an indexed-key slot. Deliberately not applied to the chat or
reminder paths: those need the oldest durable facts ("grows basil in containers",
"zone 9b"), which a recency bound would hide.
- build_section_summary keeps the directly written record terse. One album turn
yields several records describing the same bed (this one plus whatever extraction
derives), and embedding the agent's full reply made ours the longest while adding
no fact the others lacked.
- Document why memoryStrategyId is omitted on BatchCreateMemoryRecords: supplying it
filters metadata to that strategy's schema and silently drops the rest, which
would discard photo_count.
- Drop the dead derive_episodic_namespace helper and EPISODIC_SEGMENT.
Verified against the live service, not only in tests: section=north_bed -> 1,
plants CONTAINS basil -> 1, section=missing_bed -> 0, unindexed photo_count ->
ValidationException; temporal AFTER 2020 -> 1 and AFTER tomorrow -> 0; a deep-
namespace record is returned via namespacePath but not via namespace. Change sets
were checked for Replace=False on AgentCoreMemory before each apply, so stored
memories were never at risk, and get_memory confirms the final strategy set.
Note record indexing takes roughly 60-75 seconds, so an immediate post-write query
returns nothing and can look like a failure.
161 agent-container + 39 webhook tests pass; template validates; bandit 0 HIGH.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Garden sections were saved with custom metadata that couldn't filter retrieval, so "which beds have basil?" fell back to similarity search over prose. This makes those dimensions actually queryable.
IndexedKeys(type,section,plants) on the memory resource. Only indexed keys can appear in ametadataFiltersexpression.retrieve(). AgentCore applies them before the vector search, so they narrow the candidate set instead of trimming top-K results.MetadataSchemaper strategy so extracted records carry the same dimensions the app writes.CreateEventfor values known at event time (a captioned album'ssection), so extraction propagates.SummaryMemoryStrategy— It produced nothing in weeks of real use, not really necessary for this use case.Worth noting
IndexedKeys; otherwise you getValidationException: not a valid filter key. Indexed keys are additive-only, capped at 10, and can never be removed — hence only three.namespacematches the exact namespace, not a prefix (the API docs imply otherwise). Anything nested deeper is silently omitted, so retrieval usesnamespacePath.