Skip to content

[Bug] EpisodicNode.episode_metadata is documented as public surface but is never persisted or read -- always None #1769

Description

@C-Bjorn

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

  • Bug fix
  • New feature
  • Breaking change
  • Documentation

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 modelgraphiti_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 writegraphiti_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 passedgraphiti_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 backEPISODIC_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 pathadd_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

  • Reproduced against the installed 0.29.3; fork and installed package have identical line numbers at every cited location
  • Confirmed by direct database inspection that the property is absent on stored nodes
  • Root cause traced through all four provider save branches and the read path
  • Fix implemented (not in scope for this issue — filed to establish the record before a PR)

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions