Library// diagnostic

The record was deleted and the assistant still quotes it

In short

A delete that stops halfway leaves the previous state, which is indistinguishable from a system working. No missing row to count, no exception to catch. So the fault is located rather than inferred: put 1 tracer record with a unique token through the real delete path, then query every store in order. The first store still returning the token is the hop that swallowed it.

Key takeaways

  • A failed delete produces no error and no gap. It produces yesterday's answer, which looks like success.
  • Trace a record you created, not the one in the complaint. That record's history is already contaminated.
  • The first store that still returns the token is the guilty hop. Everything downstream of it is innocent.
  • Deleting a document is 1 write. Undoing it is N chunks, N vectors, a summary and every cached answer.
  • Most sources emit no delete event. Absence from tomorrow's export is the only signal you are given.

Somebody deleted the supplier record on the 3rd. On the 24th the assistant quoted its payment terms, with a citation, out of a document nobody can open any more. Nothing errored across those 21 days: a pipeline built around inserts and updates has nothing to report when a record stops arriving.

Deletes fail differently from every other operation. A failed insert leaves a gap somebody eventually counts. A failed delete leaves the previous state, which is what a healthy system looks like. The work is not repair, it is location: finding the one hop where the delete stopped, out of the 5 or 6 most builds contain.

Put one tracer through the real delete path

Do not start from the record in the complaint. It may have been edited, re-exported, merged or re-created since, and each of those muddies the evidence. Start from a record you control.

  1. Mint a tracer. Create 1 record with a nonsense token such as ZZQX-TRACER-0471 in a field that reaches indexed text. A token with no natural-language neighbours is exact-matchable in every store you are about to query.
  2. Let it land completely. Wait one full cycle of every scheduled stage, then confirm the assistant can answer with it. If it never became answerable, stop: that is an ingest problem.
  3. Write down where it lives. Note its identifier in each store — source id, raw payload, document id, chunk ids, vector ids, any summary mentioning it. Compiling the list usually surfaces a store nobody had on the diagram.
  4. Delete it the way a user does, through the same button or API call the business uses. A delete issued straight against the database proves nothing about the path real deletions take.
  5. Note the wall-clock time and wait one full cycle again, so a slow stage is not mistaken for a broken one.
  6. Query every store from step 3 by exact token, working outward from the source. The first that still returns it is the guilty hop; everything downstream of it is innocent.
  7. Ask the question that used to retrieve it. Clean stores plus a stale answer means a cache, or an answer never retrieved at all — the failure in answering from memory rather than context.
StoreHow you query itA hit here means
Source systemIts own search, and an API read by idThe delete never committed, or it set a flag the API still returns as an ordinary record
Raw landing zoneGrep retained payloads by arrival dateThe source stopped sending the record and never said it was gone
Chunk tableExact match on chunk text, plus chunk counts per parent idThe parent row went and its children stayed. The most common hop by a distance
Vector indexFetch by the vector ids from step 3The chunk rows went and the index was never told, or the delete sits inside its consistency window
Answer cacheReplay the question, then repeat it with the cache bypassedNothing maps a record id back to the answers built on it
Derived summariesFull-text search of the generated textAn artefact generated last month still contains the record, and nothing regenerates it
Where to look for the tracer token, and what a hit in each store tells you

If every store clears but takes days, that is not a delete failure. It is lag, measured as an age distribution rather than hunted as a bug — the method is in answers that are a quarter out of date. If only the vector index lags, check whether the delete is queued behind a reindex still running after three days.

Five hops that eat a delete, and how each one looks from outside

CauseWhy the delete disappearsThe confirming checkWhere the repair lives
No delete event at sourceMost exports list what exists, not what changedDiff yesterday's key set against today's. Absent rather than flagged means there was no event to missSnapshot diffing, with a cap on how large a vanish set may be before the run halts
A soft delete nobody interpretedThe source sets a flag and keeps the row; the connector reads rows, not meaningsRead the raw payload after deletion for a status or valid-to field that movedThe ingest mapping, plus a rule that an unrecognised status quarantines rather than passes
Document deleted, chunks orphanedNo cascade from the parent row, and no foreign key reaches the vector indexCount chunks whose parent id no longer resolves. Non-zero is decisiveA delete handler fanning out over recorded child ids, verified per store
A cache with no invalidation pathThe cache is keyed on the question, and nothing links a record id to the answers quoting itCompare a cached response against the same call with caching offShort expiry plus explicit purge, or a cache key carrying a corpus version
A derived artefact nobody regeneratesSummaries and entity profiles are built once, then treated as sources themselvesFull-text search every derived artefact, including ones served outside the assistantDerive on read, or record which source ids each artefact was built from
The 5 recurring causes, the confirming check for each, and where the repair belongs

One possibility sits underneath all 5 rows: the delete landed correctly, on 1 of the 3 records describing the same thing. Deleting a record does not delete an entity when nothing ever decided they were one — a customer under three spellings.

A soft delete is a flag, and a flag means nothing to a reader never told about it

The source did not lie. It sent the record with a field saying it is gone, and the connector — which maps the 6 fields it cares about and ignores the rest — passed it through as ordinary content. This is the cause most often misdiagnosed as a broken index, because the payload arrives complete.

  • A boolean that flips. Easy to handle once known, invisible until somebody reads a payload from after the deletion.
  • A status enum that gains a value. The record moves from active to archived, and a mapping knowing only active treats the new value as ordinary content.
  • A validity window that closes. Nothing is flagged: an end date moves into the past, and correctness depends on the reader comparing it against now.
  • A move rather than a change. The record goes to a deleted-items table the connector never queries, so it is neither present nor announced.
  • Permission revoked instead of removed. The record exists, the service account still reads it, and every human calling it deleted is also right.

Which of these your source uses is a property of the source, not a preference, and it decides what change detection can see at all — the choice examined in how a pipeline decides something changed and change data capture for a document corpus.

An insert that fails leaves a hole somebody counts. A delete that fails leaves yesterday's answer, and nobody counts those.

The copies nobody drew on the architecture diagram

Every trace that comes back clean at the index and dirty at the interface ends in the same place: a copy that exists for speed and was never listed as a store.

  • The answer cache. Keyed on question text, holding a generated response and its citations, and usually the last thing anyone purges.
  • Autocomplete dictionaries. Built from indexed terms on their own schedule, so a deleted supplier keeps offering itself in the search box.
  • Materialised summaries and entity profiles. Generated from documents, stored as text, then retrieved as though they were documents.
  • Scheduled exports. A spreadsheet refreshed nightly, which nobody counts as part of the AI system until it quotes a deleted record.
  • Search and prompt logs. Not serving traffic, but holding the content, and in scope for anything beyond a mechanical delete.

Give the delete a receipt from every store

The design that ends this class of bug is unglamorous: treat deletion as a record rather than an absence. Write a tombstone carrying the source id, the timestamp and the run that observed it, fan it out over the stored child ids, and require every store to return a verification count of 0 before the deletion is marked complete.

  • Make it idempotent. Deleting twice must be a no-op and must not fail the run, for the reason in idempotent ingestion.
  • Carry the run identifier. A deletion you cannot attribute to a run is one you cannot audit, which is the argument for lineage on every served passage.
  • Alert on unacknowledged deletes. A tombstone past its propagation window with a store still unconfirmed is the shape of alert in what to alert on in an ingest pipeline.
  • Keep the tracer running on a schedule. A delete path is only known to work on the day it was last exercised.

The full fan-out design is the guide on making a delete travel to the last cache. Where the vector index is a managed service, its deletion semantics and consistency window are somebody else's engineering decisions and can change under you — one of the things weighed in which capabilities are worth owning outright. The verification harness itself is ordinary software: a page that takes a record id and shows a row per store with a count, which we treat as part of the build in MVP and product builds.

What a clean trace still does not prove

A tracer that clears every store proves the mechanical path works today, for the shape of record you tested. It says nothing about copies outside the serving path: backups, replicas, prompt and trace logs, exports, anything a person downloaded. Answering an erasure request to a standard somebody else will audit is a different exercise, handled in carrying a deletion through every copy you made and what a retention schedule covers.

It also does not prove updates propagate. Deletion and modification travel different paths and fail independently, so a working delete path says nothing about a document updated at source that still serves old text. Both sit in data readiness and pipelines, part of the engineering library.

Frequently asked questions

Short answers to the follow-ups this page tends to raise.

Why is a deleted document still returned by search?

Because the deletion stopped at one hop and nothing downstream was told. The candidates are a source that never emitted a delete event, a soft-delete flag the connector ignored, a document row removed without its chunks, a cache with no invalidation path, or a summary nobody regenerates. Query each store by an exact token from the record, outward from the source: the first that still returns it is the guilty one.

How do I verify a deletion reached every store?

Delete a tracer record you created and query each store for its unique token. Let it flow through until the assistant can answer with it, note its identifier in every store, delete it the way a user would, wait a full cycle, then check each store. A verification query returning 0 rows per store is the only evidence that counts.

Does deleting a document remove its chunks and vectors automatically?

Not unless something was built to do it. The chunk table often has no cascade from the document row, and a vector index is usually a separate system with no foreign keys, so removing the parent leaves orphaned children that retrieve normally. Count chunks whose parent id no longer resolves; anything above 0 means the fan-out is missing.

The source system uses soft deletes. What does that break?

It breaks any connector that reads rows rather than meanings. A soft delete keeps the record and marks it, so the payload arrives complete and a mapping that knows only its 6 fields passes it through as live content. Fix it at the ingest mapping, and quarantine any status value the mapping does not recognise rather than defaulting it to active.

  • deletion
  • pipelines
  • cache invalidation
  • diagnosis
// shipped work

The work behind this page

Builds from our portfolio that this page draws on.

Read next

Working on something in this space?

Tell us where you are in a sentence or two. We'll tell you honestly whether we're the right team, and what a sensible first slice of the work looks like.

Start the conversation