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.
- 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.
- 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.
- 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.
- 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.
- Note the wall-clock time and wait one full cycle again, so a slow stage is not mistaken for a broken one.
- 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.
- 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.
| Store | How you query it | A hit here means |
|---|---|---|
| Source system | Its own search, and an API read by id | The delete never committed, or it set a flag the API still returns as an ordinary record |
| Raw landing zone | Grep retained payloads by arrival date | The source stopped sending the record and never said it was gone |
| Chunk table | Exact match on chunk text, plus chunk counts per parent id | The parent row went and its children stayed. The most common hop by a distance |
| Vector index | Fetch by the vector ids from step 3 | The chunk rows went and the index was never told, or the delete sits inside its consistency window |
| Answer cache | Replay the question, then repeat it with the cache bypassed | Nothing maps a record id back to the answers built on it |
| Derived summaries | Full-text search of the generated text | An artefact generated last month still contains the record, and nothing regenerates it |
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
| Cause | Why the delete disappears | The confirming check | Where the repair lives |
|---|---|---|---|
| No delete event at source | Most exports list what exists, not what changed | Diff yesterday's key set against today's. Absent rather than flagged means there was no event to miss | Snapshot diffing, with a cap on how large a vanish set may be before the run halts |
| A soft delete nobody interpreted | The source sets a flag and keeps the row; the connector reads rows, not meanings | Read the raw payload after deletion for a status or valid-to field that moved | The ingest mapping, plus a rule that an unrecognised status quarantines rather than passes |
| Document deleted, chunks orphaned | No cascade from the parent row, and no foreign key reaches the vector index | Count chunks whose parent id no longer resolves. Non-zero is decisive | A delete handler fanning out over recorded child ids, verified per store |
| A cache with no invalidation path | The cache is keyed on the question, and nothing links a record id to the answers quoting it | Compare a cached response against the same call with caching off | Short expiry plus explicit purge, or a cache key carrying a corpus version |
| A derived artefact nobody regenerates | Summaries and entity profiles are built once, then treated as sources themselves | Full-text search every derived artefact, including ones served outside the assistant | Derive on read, or record which source ids each artefact was built from |
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
The work behind this page
Builds from our portfolio that this page draws on.
AskVault
An AI internal knowledge-search platform that answers employee questions from your own docs — grounded in citations, with knowledge gaps surfaced and deflection tracked.
Productivity AINotewell
An AI meeting assistant that records and transcribes every meeting, extracts the decisions and action items, assigns owners and due dates, and tracks follow-through until it's done.
Productivity AIRead next
- Someone updated the document and the system still serves the old textThe indexing job is rarely the culprit. A timestamp that never moved, a hash over the wrong bytes, or an edit the extractor discards all leave the pipeline correctly deciding there is nothing to do.diagnostic
- Change data capture, applied to documents rather than rowsChange data capture makes downstream work proportional to what changed rather than to the size of the corpus. Documents make it hard, because they have no equivalent of a database write log.definition
- The reindex has been running for three days and is still not donePercent complete cannot tell a slow job from a stuck one. Committed records per minute can, and the curve's shape names the cause before you open a single log file.diagnostic
- The figures in the answer are a quarter old and nobody noticedA vague sense that the data feels old becomes actionable the moment you measure it: sample what was actually served, subtract source-modified from indexed-at, and read the distribution.diagnostic
- A freshness SLA is a promise about the worst case, not the averageFreshness is the age of the data behind an answer when it is served, held under a stated ceiling. Latency is how fast a run finishes, and the two can disagree by a week.definition
- A subset of the PDFs came through as gibberish and nobody lookedA chunk of mojibake embeds happily, indexes happily and retrieves for nothing. No stage errors, so the only defence is a screen that reads the text before it is indexed.diagnostic
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