Finding · Published

Chunk identity across four retrieval libraries: four different answers, and no shared contract to inherit

The four open-source retrieval libraries examined here disagree about what a chunk is called, what its name is a function of, and whether it survives a rebuild. None offers a cross-runtime identity contract — so a corpus expected to outlive its retrieval stack has to own that contract itself.

Finding MCG-F-0003 Published 2026-09-06 Confidence: provisional Joseph Walker

Truth boundary

This is a provisional Finding. The result held under the stated method and scope, and has not yet been repeated or tested against a wider sample.

A retrieval corpus has two jobs that pull against each other. It has to be addressable, so an answer can cite a passage and a reader can go and read it. And it has to be rebuildable, so that when the embedding model is replaced — and it will be — stored vectors can be matched to the text they were computed from, or shown not to match any more.

Both are questions about identity. So before choosing a model, a vector store or a chunking strategy, it is worth asking a narrower question:

What must remain stable in a RAG-ready publication corpus if embedding models and retrieval runtimes are expected to change?

Four libraries, four answers

Four open-source retrieval libraries were read at immutable commits rather than from documentation, because this behaviour lives in default arguments and decorators rather than in prose. No claim is made here about their relative adoption — the conclusion rests on how far apart their answers are, not on how many people use them.

LibraryIdentity on the default pathReproduced by an independent rebuild?A function of the vector?
LlamaIndex 0.14.24uuid.uuid4() unless the caller supplies oneNoNo
Unstructured 0.27.5sha256(filename + text + page + sequence)[:32]YesNo
Haystack 3.1.1hash of content and metadata — and of the embedding, when one is present at creationYes, given the same inputsConditionally
LangChain 1.6.2optional; None by defaultn/an/a

LlamaIndex assigns a fresh random identifier every time. That is not a mistake — it is unique, which is what a library holding the chunk and the vector in one process needs. It is simply not an address: rebuild the corpus and every stored vector is orphaned, not because anything changed but because the identifiers were never a function of anything.

Unstructured is the closest thing here to a durable answer, and it is worth being precise about it. The Element.id property does fall back to a UUID — but that is the bare-object fallback, not what partitioning produces. The partition pipeline is decorated with process_metadata(), which reads unique_element_ids (default False) and calls assign_and_map_hash_ids() when it is false. So deterministic hashed identity is the default and UUIDs are the opt-in. The hash covers filename, text, page number and per-page sequence, which makes it reproducible and indifferent to any model — but also positional, so inserting content earlier in a document can move the identifiers after it.

Haystack repays careful reading, and punished careless reading here. Its _create_id() does hash the embedding and sparse embedding along with the content and metadata. It does not follow that changing model re-identifies anything: __post_init__ is self.id = self.id or self._create_id(), and the embedders attach vectors with replace(doc, embedding=...), which carries the existing id straight through. Embedding and re-embedding preserve identity. What is true is narrower: a document whose id is generated while a vector is already attached has an identifier that is a function of that vector — a real coupling between identity and representation, on one construction path rather than as a lifecycle guarantee.

LangChain takes no position. Document is page content, a free-form metadata dict and an optional id. That is a reasonable interchange shape and it is not a contract; nothing about provenance is defined, so nothing about provenance is guaranteed.

What that heterogeneity means

The useful result is not that any of these is wrong. Each is coherent for a framework that owns the chunk, the vector and the index together.

The result is that there is nothing here to inherit. Four libraries give four different answers to "what is this passage called", ranging from random to positional to conditionally model-dependent to undefined. A corpus that expects to outlive whichever runtime is currently reading it cannot adopt one of these as its identity contract, because none of them is offered as a contract between systems in the first place.

So identity has to be an explicit property of the corpus. And once it is written down deliberately, it turns out one identifier cannot do the job.

Why one identifier is not enough

The obvious correction to random identity is content addressing: derive the identifier from the text and it becomes reproducible. That fixes the rebuild problem and creates a worse one. If the identifier is the text digest, then correcting a typo moves the address. A reader who cited a section last year finds their reference no longer resolves — not because the section was withdrawn, but because a comma changed.

The tension resolves by noticing that two different things were being asked of one identifier:

  • a public referent — the publication and its section anchor — which is what a citation points at, which corresponds to a real anchor in the published HTML, and which survives rewording, repartitioning and a rename of the heading itself;
  • a derived chunk identity — the referent, the position within that section, and a digest of the exact text — which is what a vector is keyed by, and which moves whenever either the text or the partitioning moves.

The public referent is deliberately not unique: every chunk cut from one section shares it, because they are all in that section and a reader citing the section means the section. Uniqueness is the derived identifier's job.

The second identifier's property is what pays for the arrangement. A vector stored against it is self-invalidating: after an edit the old vector is attached to an identifier the corpus no longer contains. Nothing has to remember to expire it, and no separate freshness table has to be maintained and trusted.

Neither identifier contains a model name, a vector, a cluster, a timestamp or a random component.

What was demonstrated

The design was implemented over the real Masters publication corpus and tested against published pages rather than fixtures.

Editing a sentence of published prose moves the derived identity and leaves the public referent untouched. Editing page furniture — an eyebrow label, a date line — moves nothing at all, which matters more than it sounds: an editor adjusting a caption should not silently invalidate a page's embeddings.

The sharper test is repartitioning. Inserting a whole paragraph earlier in a long section changes how the section groups: the positions after it shift and several chunks' text changes. The derived identities move, as they must. The public referent does not, because the anchor did not — a citation recorded before the edit still lands exactly where it did. A single-word edit never exercises that, which is why the first version of this test was not enough.

The sharpest test exposed a defect rather than confirming a design. The public referent was only as durable as the anchor, and the anchors were being slugified from the heading's wording — so renaming a heading silently moved every citation aimed at it, and no test noticed because no test renamed a heading. The fix belongs in the publication layer rather than in the corpus: the constrained Markdown grammar now carries an explicit section id,

## Four libraries, four answers {#four-libraries-four-answers}

which is consumed rather than displayed, and a published record whose body headings lack one fails the build. Display text and address are now independent things, and the test that proves it renames a heading and checks that the anchor, the section URL and the locator all stay put while the visible heading and the heading path change.

A second result came out of the same work. The site already had a deterministic extractive passage layer for its reference desk, with hard-won rules about what counts as published prose and what is furniture. Rather than write a second parser with its own opinion, the policy was shared and the implementations left separate — and the two extractors were then shown to produce identical text across all 119 sections of all 14 public pages. Sharing the boundary rule while keeping the extractors independent proved better than consolidating them, because the older layer's integrity test depends on re-deriving text through a genuinely different code path.

What this does not settle

No embedding was generated. The claim that a text-derived identity makes a stale vector visible is a property of how the identifier is constructed, not an observation of a migration that was performed. That comes next, and this order is deliberate: a contract a model attaches to should exist and be stable before the model is chosen, or the model's output shape quietly becomes the schema and the first migration becomes a migration of the corpus.

Nor does it say whether the chunks are good — whether the unit suits retrieval, whether the boundaries fall where a reader would put them. Those need an evaluation with an unseen holdout, over a corpus larger than three publications.

There is also a methodological result worth keeping. The first pass of this review got two of the four libraries wrong, in the same way both times: it read a default in isolation instead of the path that actually runs. Unstructured's UUID fallback is real but is not what partitioning does; Haystack's id formula is real but does not imply the lifecycle that was inferred from it. Reading a data class is not the same as reading a lifecycle, and a prior-art review that stops at the first plausible line of code will produce confident, wrong comparisons.

What the corrected evidence does settle is narrower and load-bearing: an institution that expects to change retrieval models should not let the retrieval library decide what its passages are called. Identity is a property of the record, not of the tool currently reading it.

Research question

What must remain stable in a RAG-ready publication corpus if embedding models and retrieval runtimes are expected to change?

Scope

The chunk and document identity semantics of four open-source retrieval libraries, read at immutable commits, assessed against one question: what is an identifier a function of, and does it survive an independent rebuild? Retrieval quality, chunking quality and embedding model quality were all out of scope. No embedding was generated, and no library was installed; the one lifecycle question that could not be settled by reading was settled with a standard-library reproduction of the pinned source.

Systems evaluated

  • LlamaIndex — llama-index-core 0.14.24
  • Unstructured 0.27.5
  • Haystack — haystack-ai 3.1.1
  • LangChain — langchain-core 1.6.2

Method

  • Select four open-source retrieval libraries whose document models a corpus might otherwise inherit. No claim is made about their relative adoption; the conclusion rests on the differences between them, not on how many people use them.
  • Resolve each to an immutable commit and read the identity code there, rather than reading documentation or a moving branch.
  • For each, ask two questions: what is the identifier a function of, and does an independent rebuild reproduce it?
  • Where reading the formula could not settle the lifecycle, reproduce the dataclass semantics from the pinned source with the standard library and observe the result directly.
  • Implement the resulting two-identifier design over the real Masters publication corpus.
  • Demonstrate the required properties against published pages: edit prose, edit page furniture, and repartition a section by inserting a block earlier in it.

Result

The four libraries give four materially different answers, and none of them is a contract another system could adopt. LlamaIndex generates a random UUID per node unless the caller supplies one, so an independent rebuild does not reproduce identity. Unstructured's partition pipeline defaults to a deterministic hash of filename, text, page number and per-page sequence — reproducible and independent of any model, but positional, so structural insertion can move it. Haystack preserves an existing id on the reviewed embedding path, but an id generated while a vector is present is a function of that vector, which couples identity to representation on that construction path. LangChain leaves the id optional and imposes no provenance semantics at all. The consequence is not that these libraries are wrong for their own purposes: it is that there is no shared identity contract to inherit, so a corpus expected to outlive its retrieval runtime must define identity explicitly, and the useful shape is two identifiers rather than one — a stable public referent that citations point at, and a revision-specific derived identity that a stored vector is keyed by.

Negative results

  • No open interchange convention for provenance-preserving chunk identity was demonstrated in this review. What the search returned was library-internal data models, none of which is offered as a contract between systems. That is a weaker prior-art position than the claim and evidence layers examined in MCG-F-0001, where W3C PROV and the Wikibase statement model exist as standards.
  • None of the four libraries is a substitute for a corpus contract, and none was adopted as a runtime. Doing so would take a Python dependency tree into a toolchain that has none, in exchange for reading HTML, grouping blocks and writing JSON — which is not the difficult part.
  • Content addressing alone does not work, despite being the obvious correction to random identity. Deriving the identifier only from the text makes citations fragile: fixing a typo would move an address a reader had already recorded.
  • The first pass of this review reached two wrong conclusions, both from reading a default in isolation rather than the path that actually runs. It reported Unstructured as defaulting to UUIDs, when the partition pipeline defaults to deterministic hashes and UUIDs are opt-in; and it inferred from Haystack's id formula that changing model re-identifies every document, when an existing id is preserved through the embedding step. Reading a data class is not the same as reading a lifecycle.
  • The reviewed libraries are not badly designed. A framework that owns the chunk, the vector and the index in one process has no reason to separate identity from representation. The coupling only becomes a defect when the corpus is expected to outlive the framework.

Limitations

  • This is a source review of four libraries at four commits, plus an implementation over one small corpus. It is not a benchmark and says nothing about retrieval quality.
  • Identity behaviour is configurable in most of these libraries. The finding is about what an identifier is a function of on the default path, not about what each library can be made to do.
  • No embedding model was run and no library was installed. The claim that a text-derived identity makes a stale vector visible is a property of the construction, not an observation of a migration.
  • The Haystack lifecycle result comes from a standard-library reproduction of the pinned source rather than from executing the library, and only one embedder implementation was read. It establishes the dataclass semantics, which is the load-bearing step, and the behaviour of the reviewed embedding path — not the behaviour of every embedder and document store in the ecosystem.
  • Unstructured's deterministic identity was read rather than executed; the claim is about what the code does on the default partition path.
  • The corpus tested contains three publications. Chunking behaviour at larger scale or over structurally different material is unmeasured.
  • Library versions move quickly. Every statement here is pinned to a commit and is a fact about that commit only.

What would change this conclusion

  • An open interchange convention for provenance-preserving chunk identity emerges with real adoption, which would move this from 'define it yourself' to 'export to it'.
  • A reviewed library adopts a durable, model-independent identity on its default path, which would narrow the heterogeneity this Finding describes.
  • A migration between embedding models is actually performed and the self-invalidating identity fails to make stale vectors visible in practice.
  • A retrieval experiment shows the chosen chunk unit is wrong for Masters prose, which would change the chunking policy without touching the identity conclusion.
  • The corpus grows to a size where per-chunk digests become an operational cost rather than a convenience.

Sources

The material cited in support of this record, with the date each was read. This is a bibliography, not a claim of exhaustive coverage: a reader should treat any statement here that no listed source supports as unverified. A link that has since changed does not change what was observed on that date; it changes whether the observation still holds.

  • LlamaIndex — llama_index/core/schema.py at v0.14.24 Accessed 2026-09-05

    MIT. BaseNode declares id_ with default_factory=lambda: str(uuid.uuid4()), so identity is random unless the caller supplies one. NodeRelationship is SOURCE/PREVIOUS/NEXT/PARENT/CHILD and RelatedNodeInfo carries the target's hash.

  • Unstructured — Element.id and id_to_hash at 0.27.5 Accessed 2026-09-05

    Apache-2.0. OBJECT FALLBACK: the Element.id property lazily assigns uuid.uuid4() when no id was set. id_to_hash computes sha256(filename + text + page_number + sequence_number)[:32].

  • Unstructured — process_metadata partition default at 0.27.5 Accessed 2026-09-06

    NORMAL PARTITION OUTPUT: the decorator reads unique_element_ids (default False) and calls assign_and_map_hash_ids(elements) when it is false, so deterministic hashed identity is the default and UUID identity is opt-in. Sequence numbers are assigned per page by grouping element page numbers.

  • Haystack — haystack/dataclasses/document.py at v3.1.1 Accessed 2026-09-05

    Apache-2.0. _create_id hashes content, blob, mime type, meta, embedding and sparse_embedding together. __post_init__ is self.id = self.id or self._create_id(), so an existing id is preserved.

  • Haystack — OpenAIDocumentEmbedder at v3.1.1 Accessed 2026-09-06

    The one embedder implementation read for this review. It attaches vectors with replace(doc, embedding=...), which carries the existing id through __post_init__ unchanged. Other embedders at this version were not inspected.

  • LangChain — langchain_core/documents/base.py at langchain-core 1.6.2 Accessed 2026-09-05

    MIT. Document is page_content plus a free-form metadata dict and an optional id defaulting to None; no provenance or identity semantics are imposed.

  • W3C PROV-O: The PROV Ontology Accessed 2026-09-05

    Derivation and attribution relationships; the same instinct as recording what an edge pointed at rather than only that it pointed.

Related records

Masters claims and sources are institutional evidence objects held once and referenced here by their stable identifiers. Correcting one of them corrects every record that cites it. They are distinct from the bibliography above, which is the material consulted for this publication.