Summary
EpisodicNode.episode_metadata is declared on the model (graphiti_core/nodes.py:329-332) and was listed in the 0.29.0 release notes as new public surface, alongside Graphiti.summarize_saga() and the fact_triple episode type. It has no wiring behind it: it is dropped before the database write, never populated on read, and has no input path from any public API. Every episode returned by retrieve_episodes() has episode_metadata: None regardless of what a caller does.
A repo-wide search for the literal string episode_metadata returns exactly one hit — the field declaration itself.
Type of Change
Objective
Either wire episode_metadata end-to-end (save, read, and an add_episode() parameter), or remove it from the model and correct the 0.29.0 release notes. The current state advertises a feature that silently does nothing.
What I was trying to do
Attach caller-supplied structured metadata (source document path and vault identifier) to episodes, so a published graph replica could cite source documents without a side-channel lookup file. The 0.29.0 release notes describe EpisodicNode.episode_metadata as being for exactly this kind of custom filtering key.
What I expected
Either a parameter on add_episode() accepting the metadata, or the ability to set episode_metadata on an EpisodicNode and have it round-trip through save and retrieve.
What actually happened
No input path exists, and any value set on the model is discarded before the write.
Root cause
1. Declared on the model — graphiti_core/nodes.py:329-332:
episode_metadata: dict[str, Any] | None = Field(
description='customer-defined metadata key-value pairs for filtering',
default=None,
)
2. Dropped before write — graphiti_core/nodes.py:341-354, EpisodicNode.save() builds episode_args with nine keys (uuid, name, group_id, source_description, content, entity_edges, created_at, valid_at, source). episode_metadata is absent, so it is never passed as a query parameter.
3. Not writable even if it were passed — graphiti_core/models/nodes/node_db_queries.py:30-66, get_episode_node_save_query() hardcodes an explicit SET n = {...} naming the same nine properties, in all four provider branches (Neo4j, FalkorDB, Kuzu, Neptune). This differs from EntityNode, whose save query (node_db_queries.py:181-190) spreads a caller-built dict via SET n = $entity_data and therefore does support arbitrary properties.
4. Not read back — EPISODIC_NODE_RETURN (node_db_queries.py:112-122) returns the same nine properties, and get_episodic_node_from_record() (nodes.py:1028-1047) constructs the EpisodicNode without passing episode_metadata, so it always falls back to the field default of None.
5. No input path — add_episode() (graphiti_core/graphiti.py:980-998) has no metadata or episode_metadata parameter, and does not reference the field anywhere in its body. The bulk-add path does not either.
Reproduction
Requires only a database and a single episode:
import asyncio, os
from datetime import datetime, timezone
from graphiti_core.graphiti import Graphiti
async def main():
g = Graphiti(
uri=os.environ['REPRO_NEO4J_URI'],
user=os.environ['REPRO_NEO4J_USER'],
password=os.environ['REPRO_NEO4J_PASSWORD'],
)
await g.add_episode(
name='repro-episode',
episode_body='A short episode used to inspect stored properties.',
source_description='repro',
reference_time=datetime.now(timezone.utc),
group_id='repro-episode-metadata',
)
episodes = await g.retrieve_episodes(
group_ids=['repro-episode-metadata'],
last_n=1,
reference_time=datetime.now(timezone.utc),
)
print('episode_metadata:', episodes[0].episode_metadata)
records, _, _ = await g.driver.execute_query(
"MATCH (e:Episodic {group_id: 'repro-episode-metadata'}) RETURN keys(e) AS k"
)
print('stored property keys:', records[0]['k'])
await g.close()
asyncio.run(main())
Observed against graphiti-core 0.29.3, Neo4j 5.26-community:
episode_metadata: None
stored property keys: ['created_at', 'group_id', 'uuid', 'valid_at', 'name', 'content', 'source', 'source_description', 'entity_edges']
There is no way to make that first line print anything else through the public API — the field has no setter path, and a hand-constructed EpisodicNode with the field set loses it at save().
Testing
A fix that wires the field should be validated by setting metadata on episode creation, retrieving the episode, and asserting the value round-trips — plus confirming the property is present in keys(e) on the stored node. A fix that removes the field instead should be accompanied by a release-notes correction, since 0.29.0 announced it publicly.
Breaking Changes
None for wiring it — the field already exists with a None default, and adding an optional add_episode() parameter is additive. Removing the field would be breaking for anyone reading it, though today it can only ever return None.
The mechanism already exists in this codebase for the other node type: EntityNode spreads a caller-built dict into entity_data and writes SET n = $entity_data (node_db_queries.py:181-190), reads it back with properties(n) AS attributes (node_db_queries.py:283-291), and pops the reserved field names in graphiti_core/driver/record_parsers.py. Applying that same round-trip to EpisodicNode — with the nine existing fields as its reserved set — would make episode_metadata behave consistently with entity attributes rather than introducing a new pattern. Worth noting only that dict[str, Any] permits nested dicts and lists-of-dicts, which Neo4j cannot store as node properties at all; entity attributes are already under that same constraint, so whatever handling they use applies here too.
Checklist
Related Issues
Introduced in PR #1422 ("forward-port graphiti_core improvements from internal fork") and announced in PR #1444's 0.29.0 release notes. No existing issue found reporting that the field is non-functional.
Summary
EpisodicNode.episode_metadatais declared on the model (graphiti_core/nodes.py:329-332) and was listed in the 0.29.0 release notes as new public surface, alongsideGraphiti.summarize_saga()and thefact_tripleepisode type. It has no wiring behind it: it is dropped before the database write, never populated on read, and has no input path from any public API. Every episode returned byretrieve_episodes()hasepisode_metadata: Noneregardless of what a caller does.A repo-wide search for the literal string
episode_metadatareturns exactly one hit — the field declaration itself.Type of Change
Objective
Either wire
episode_metadataend-to-end (save, read, and anadd_episode()parameter), or remove it from the model and correct the 0.29.0 release notes. The current state advertises a feature that silently does nothing.What I was trying to do
Attach caller-supplied structured metadata (source document path and vault identifier) to episodes, so a published graph replica could cite source documents without a side-channel lookup file. The 0.29.0 release notes describe
EpisodicNode.episode_metadataas being for exactly this kind of custom filtering key.What I expected
Either a parameter on
add_episode()accepting the metadata, or the ability to setepisode_metadataon anEpisodicNodeand have it round-trip through save and retrieve.What actually happened
No input path exists, and any value set on the model is discarded before the write.
Root cause
1. Declared on the model —
graphiti_core/nodes.py:329-332:2. Dropped before write —
graphiti_core/nodes.py:341-354,EpisodicNode.save()buildsepisode_argswith nine keys (uuid,name,group_id,source_description,content,entity_edges,created_at,valid_at,source).episode_metadatais absent, so it is never passed as a query parameter.3. Not writable even if it were passed —
graphiti_core/models/nodes/node_db_queries.py:30-66,get_episode_node_save_query()hardcodes an explicitSET n = {...}naming the same nine properties, in all four provider branches (Neo4j, FalkorDB, Kuzu, Neptune). This differs fromEntityNode, whose save query (node_db_queries.py:181-190) spreads a caller-built dict viaSET n = $entity_dataand therefore does support arbitrary properties.4. Not read back —
EPISODIC_NODE_RETURN(node_db_queries.py:112-122) returns the same nine properties, andget_episodic_node_from_record()(nodes.py:1028-1047) constructs theEpisodicNodewithout passingepisode_metadata, so it always falls back to the field default ofNone.5. No input path —
add_episode()(graphiti_core/graphiti.py:980-998) has nometadataorepisode_metadataparameter, and does not reference the field anywhere in its body. The bulk-add path does not either.Reproduction
Requires only a database and a single episode:
Observed against graphiti-core 0.29.3, Neo4j 5.26-community:
There is no way to make that first line print anything else through the public API — the field has no setter path, and a hand-constructed
EpisodicNodewith the field set loses it atsave().Testing
A fix that wires the field should be validated by setting metadata on episode creation, retrieving the episode, and asserting the value round-trips — plus confirming the property is present in
keys(e)on the stored node. A fix that removes the field instead should be accompanied by a release-notes correction, since 0.29.0 announced it publicly.Breaking Changes
None for wiring it — the field already exists with a
Nonedefault, and adding an optionaladd_episode()parameter is additive. Removing the field would be breaking for anyone reading it, though today it can only ever returnNone.The mechanism already exists in this codebase for the other node type:
EntityNodespreads a caller-built dict intoentity_dataand writesSET n = $entity_data(node_db_queries.py:181-190), reads it back withproperties(n) AS attributes(node_db_queries.py:283-291), and pops the reserved field names ingraphiti_core/driver/record_parsers.py. Applying that same round-trip toEpisodicNode— with the nine existing fields as its reserved set — would makeepisode_metadatabehave consistently with entity attributes rather than introducing a new pattern. Worth noting only thatdict[str, Any]permits nested dicts and lists-of-dicts, which Neo4j cannot store as node properties at all; entity attributes are already under that same constraint, so whatever handling they use applies here too.Checklist
Related Issues
Introduced in PR #1422 ("forward-port graphiti_core improvements from internal fork") and announced in PR #1444's 0.29.0 release notes. No existing issue found reporting that the field is non-functional.