Write path
How a claim gets into the index
Before you can retrieve anything, a change in your systems of record has to travel — safely — into a vector store.
- Trigger on the commit, not the file. Your data platform writes Parquet to an S3 data lake and then fires a change event when the batch is committed in
manifest.json. We listen for that on EventBridge — not raw S3ObjectCreated, which fires per file before the batch is done and would read a half-written table. - The feed is change-data-capture, not a snapshot. Each row carries an operation — Insert, Update or Delete. We collapse the rows to each record’s latest state before indexing, so claim 88431 is one current document, not a pile of edits.
- A delete must leave the index. Deletes arrive as tombstone rows (the operation column set to
'D'). We don’t skip them — we evict that document from the store. A redacted claim disappears from retrieval, which in a regulated shop is not optional.
The data platform writes many Parquet objects per batch, then commits by updating manifest.json. Raw ObjectCreated events fire on each object before that commit — trigger on them and you read a half-written table. The batch-committed change event is the reliable signal: it only fires once the manifest is updated.
And the fork that makes a delete a first-class outcome — the tombstone becomes an eviction, not a skipped row:
Read path
Six nodes from question to citation
The query side is one explicit LangGraph graph — step through it and watch a real insurance question turn into a grounded, cited answer.
- Identity comes from the token, never the prompt. The signed token resolves to a tenant, an environment, and roles. The question can ask for anything; it can’t grant itself access.
- Each phase is a graph node. Because the pipeline is an explicit graph, every later post in this series deepens exactly one node without touching the others.
- Thin evidence means abstain. If retrieval comes back weak, the guardrail node scores groundedness and the system declines rather than guessing — then writes an audit record either way.
One question becomes several
That stepper isn’t a diagram of the code — it is the code. The graph is wired once, one node per phase:
Isolation
Why nothing crosses tenants
The same code serves every insurer and every environment — what keeps them apart is one key carried end to end. A TenantScope (tenant + env) rides on every document, chunk and query, and the vector index is namespaced tenant__env. Retrieval fails closed: a query for acme can only ever look inside acme__prod, so a globex chunk — or a dev chunk — is physically unreachable.
This whole post is one repo at one tag. Clone it, seed synthetic P&C insurance data, and ask it a question — grounded and cited, on your laptop, zero cloud.
The answer changes with --role. The adjuster gets a cited answer from the claim and the policy; the underwriter additionally sees the restricted risk memo. Same question, same index — different access, enforced at retrieval. That’s the thread Post 4 pulls on.
Teaching-grade reference implementation, not a production insurance product. It reproduces the ideas and the S3-data-lake integration shape; bring your own data and keys. MIT-licensed.
Explain it back
Your team wires the ingestion Lambda to fire on S3 ObjectCreated so new data shows up “the instant it lands.” A week later, answers occasionally cite garbled, half-populated claims. What went wrong, and what should the trigger have been?
Reveal a model answer
The data platform writes a batch as many Parquet objects and only commits it by updating manifest.json. ObjectCreated fires per object, before the commit — so the Lambda sometimes reads a table still being written and indexes partial rows. Trigger instead on the batch-committed change event (streamingBatchCompleted / batchTableWrittenOut) delivered via EventBridge: that event is the commit watermark, so you only ingest a finished batch. Bonus — the same event path is where you honor DELETE tombstones and evict redacted records, something a per-file S3 trigger gives you no clean hook for.
The spine chunks with a naive splitter — good enough to run, not to trust. How you cut a claim file decides whether the water-damage detail and its policy number land in the same passage or get orphaned into two. Next we make chunking a retrieval decision and measure it.
Continue to Chunking →