Summary
search_memory (line ~542) constructs every returned MemoryEntry with only author, content, and timestamp:
memory_events.append(
MemoryEntry(
author='user',
content=types.Content(
parts=[types.Part(text=fact)],
role='user',
),
timestamp=update_time.isoformat() if update_time else None,
)
)
MemoryEntry.custom_metadata (memory_entry.py) is never populated, even though memory.metadata -- the underlying Vertex Memory object already in scope in that same loop (memory = retrieved_memory.memory) -- carries it.
This is not a guess about what metadata is for: this same file's write path already reads MemoryEntry.custom_metadata as meaningful input. _add_memories_via_create merges it via _merge_custom_metadata_for_memory (line ~901) into what actually gets written. The write side treats the field as real; the read side drops it.
Live verification (not just source reading)
Created a memory directly against a real Vertex AI Memory Bank instance with metadata attached, then retrieved it:
Created. Now retrieving...
name: projects/.../locations/us-central1/reasoningEngines/.../memories/9019122152674164736
fact: ADK issue verification: metadata round-trip test.
metadata: {'verify_key': MemoryMetadataValue(string_value='verify_value_123')}
cleaned up
The underlying SDK's retrieved_memory.memory.metadata is genuinely populated and correct. VertexAiMemoryBankService.search_memory just never reads it into the MemoryEntry it returns.
Why this matters
A caller that writes custom_metadata at creation time -- an application-specific identifier used to correlate a memory back to its source, for example -- has no way to get it back through search_memory, ADK's own documented read path. No error, no warning; the field is silently empty on every returned MemoryEntry.
Suggested fix
memory_events.append(
MemoryEntry(
author='user',
content=types.Content(
parts=[types.Part(text=fact)],
role='user',
),
timestamp=update_time.isoformat() if update_time else None,
custom_metadata={
k: getattr(v, 'string_value', v)
for k, v in (memory.metadata or {}).items()
},
)
)
(getattr(v, 'string_value', v) unwraps Vertex's MemoryMetadataValue oneof shape -- happy to adjust to match whatever convention is preferred, or open a PR with this change if useful.)
Context
Found while building an agent-memory governance project on top of this SDK client. Filing as a report against the read-side gap specifically, since the write side already treats this data as meaningful and documented.
Summary
search_memory(line ~542) constructs every returnedMemoryEntrywith onlyauthor,content, andtimestamp:MemoryEntry.custom_metadata(memory_entry.py) is never populated, even thoughmemory.metadata-- the underlying VertexMemoryobject already in scope in that same loop (memory = retrieved_memory.memory) -- carries it.This is not a guess about what
metadatais for: this same file's write path already readsMemoryEntry.custom_metadataas meaningful input._add_memories_via_createmerges it via_merge_custom_metadata_for_memory(line ~901) into what actually gets written. The write side treats the field as real; the read side drops it.Live verification (not just source reading)
Created a memory directly against a real Vertex AI Memory Bank instance with metadata attached, then retrieved it:
The underlying SDK's
retrieved_memory.memory.metadatais genuinely populated and correct.VertexAiMemoryBankService.search_memoryjust never reads it into theMemoryEntryit returns.Why this matters
A caller that writes
custom_metadataat creation time -- an application-specific identifier used to correlate a memory back to its source, for example -- has no way to get it back throughsearch_memory, ADK's own documented read path. No error, no warning; the field is silently empty on every returnedMemoryEntry.Suggested fix
(
getattr(v, 'string_value', v)unwraps Vertex'sMemoryMetadataValueoneof shape -- happy to adjust to match whatever convention is preferred, or open a PR with this change if useful.)Context
Found while building an agent-memory governance project on top of this SDK client. Filing as a report against the read-side gap specifically, since the write side already treats this data as meaningful and documented.