Home Master Prep STAR Stories Evals & Guardrails Eval Deep Dive Guardrails Deep Dive Core Concepts Intent Recognition 中文

Interview preparation · three topics, three depths

Memory, Evaluation, Guardrails

Built from the Mines Financial project: a multi-agent retrieval chatbot over the annual reports of five ASX mining companies. Each topic runs from architecture down to the exact code in this repository.

How to use this page

Each question has two parts. Say this is a short answer written the way you would speak it — four or five short sentences, plain words. If they dig deeper is the supporting detail you pull out only when they ask a follow-up. Do not recite the bullets; they are ammunition, not a script.

Explanations are folded away so the answers stay readable. Any term marked like this opens a plain-English definition when you click it, the Glossary at the bottom unfolds all of them at once, and each topic ends with a folded if they push back section.

The three words in the job ad you should keep hearing "Safe, repeatable, production-grade." That tells you the interview is not about model cleverness. It is about whether you can prove a probabilistic system behaves, keep proving it after it ships, and stop it when it does not. Memory, evaluation and guardrails are exactly the three places where that gets decided.

What a junior answer sounds like

"I used LangGraph, added a vector database for memory, wrote some test cases, and put a check before the answer." — describes tools, not decisions.

What they want to hear

"Here is the failure I was designing against, here is the cheapest layer that catches it, here is the number that shows it worked, and here is what it still does not cover." — describes trade-offs and evidence.

1 · Memory

"Memory systems, context management" — named directly in the job description.

Level 1 · Architecture

Lead / Senior

At this level you are not describing a database. You are describing how you decided what to remember at all, and who pays when the memory is wrong.

1

How would you design memory for an agentic AI system?

Say this

The first thing I do is refuse to treat memory as one thing. "Memory" is four different problems that people give one name, and they have different storage, different lifetime, and different failure modes.

One: what did we just say — the last few turns of the conversation. Two: what has happened in this whole session, which must survive a server restart. Three: what do we know about this user or this entity that should outlive the session. Four: what have we already answered before, so we do not pay to compute it twice.

So I design each one separately, and for each I write down three things: where it is stored, how long it lives, and what happens when it is wrong. The last one matters most. A wrong memory is worse than no memory, because the system states it with full confidence.

If they dig deeper

  • State versus store. State is the working memory of one run — it dies when the run ends. A store outlives the run. Mixing them is the most common design mistake: people put long-lived facts in run state and lose them, or put per-run scratch data in a store and never clean it up.
  • Every memory entry needs provenance. Where did this come from, when, and which version of the source. Without that you cannot expire it and you cannot explain it to an auditor.
  • Memory poisoning is the real risk. If the system caches its own output, one bad answer becomes a permanent bad answer. So the write path must be gated by a quality check, not just by "we produced an answer".
  • Cost framing for a bank. Cheap memory saves latency and tokens. Wrong memory produces a confident false statement about a customer's money. Those are not symmetric, so the default should be to forget.
2

Where is the line between memory and retrieval? They both fetch text.

Say this

I split them by who produced the content. Retrieval reads from a source of truth that someone else owns — in my project, the published annual reports. Memory reads things my own system produced or inferred.

That line matters because the trust level is different. A number from an annual report is a fact I can cite with a page number. A number from my own earlier answer is a claim, and it may have been wrong when I made it.

So retrieved content can go straight into the answer with a citation. Remembered content has to be labelled, and I have to be willing to throw it away and retrieve again.

If they dig deeper

  • Practical consequence: when memory and fresh retrieval disagree, fresh retrieval wins. Memory is a shortcut, never an authority.
  • Both use the same machinery — text is turned into vectors and searched by similarity — which is exactly why people confuse them. Same technology, different trust contract.
  • The confusion has a cost: teams put summaries of their own output into the same index as source documents, and then the system cites itself.
3

You have a fixed context window. What gets in, and who decides?

Say this

The context window is the maximum amount of text the model can read in one call — roughly a few hundred pages for a modern model, but it is a hard ceiling, and everything you put in it costs money and latency.

The naive approach is to send everything: full conversation history, all retrieved documents, all memory. That breaks in two ways. It hits the ceiling, and long before that it degrades quality, because the important sentence is buried among thousands of irrelevant ones.

So I treat context assembly as an explicit, ordered budget, not a concatenation. Highest priority is the current question. Then the evidence I actually retrieved for it. Then a short summary of the older conversation. Memory hints go last, and they get dropped first when space runs short.

The key point is that this ordering is a design decision I can defend and test — not whatever the code happened to append last.

If they dig deeper

  • Why "middle of the context" is dangerous: models attend most reliably to the beginning and end of their input. Evidence buried in the middle of a large block is measurably less likely to be used.
  • Compression is lossy and that is the point. I replace ten old messages with a two-sentence summary. I have to accept that some detail is gone, so the summary prompt must name what to preserve — in my case, companies, metrics, fiscal years, conclusions.
  • Context assembly is also where injection risk enters: retrieved text is untrusted input being placed next to my instructions. See guardrails.

Level 2 · Implementation

Mid-level

Here the question changes from "how would you design it" to "how does it actually work, and what breaks". Mechanisms, thresholds, edge cases.

1

How do you stop the conversation history from growing forever?

Say this

The simplest method is a sliding window: keep the last N messages, drop the rest. It is one line of code and it is wrong for our case, because the user says "compare that with Rio" and the thing being compared was dropped.

What I use instead is a summary buffer. When the history goes past a threshold, I take everything except the most recent few messages, ask the model to summarise it in two or three sentences, delete the originals, and insert the summary in their place.

So recent turns stay word-for-word, because that is where pronouns and follow-ups point. Old turns survive as a compressed trace instead of disappearing.

The important safety rule: if the summarisation call fails, I do nothing and keep the original messages. A failed compression must never delete history.

Sliding window

Keep last 4 messages, discard older. Cheap, deterministic, no model call. Silently destroys the reference target of any follow-up question.

Summary buffer

Keep last 4 verbatim, replace older with one generated summary. Costs one model call and is lossy, but follow-ups still resolve.

If they dig deeper

  • Trigger on tokens, not message count, if messages vary wildly in length. Mine triggers on count because turns here are fairly uniform — that is a simplification I can name.
  • Summaries compound. If you summarise a summary repeatedly, detail decays each round. A production version keeps the raw transcript in cheap storage and re-summarises from the original, not from the last summary.
  • Deleting a message must be an explicit instruction to the state layer, not a rewrite of the list, so that concurrent branches do not resurrect it.
2

Explain a semantic cache. When does it help, and when does it hurt you?

Say this

A normal cache matches the exact string. That is useless for natural language, because "BHP revenue in FY2024" and "How much did BHP earn in FY2024?" are different strings asking the same thing.

A semantic cache fixes that by comparing meaning. I turn the question into an embedding — a list of numbers that represents its meaning — and compare it with the embeddings of past questions. If the similarity is high enough, I reuse the old answer.

It helps a lot in a domain like this, because a handful of questions cover most of the traffic and each miss costs several model calls plus retrieval.

Where it hurts is the near-miss. "BHP revenue FY2024" and "BHP revenue FY2023" are almost identical as sentences but completely different as questions. A cache tuned too loose will confidently serve the wrong year. That is why I do not use one threshold — I use two.

If they dig deeper

  • Two-tier design. Above a high bar, return the cached answer directly and skip retrieval. In a middle band, run the full pipeline anyway but pass the old answer in as a hint. The middle band captures value without betting the answer on a similarity score.
  • The dangerous failure is silent. A cache miss just costs money; a false hit produces a wrong answer with no error anywhere in the logs. So false-hit rate is the metric that matters, not hit rate.
  • How to actually set the threshold: build a labelled set of question pairs — same-intent and different-intent — and pick the cut-off from that curve. Hand-picking a number is a placeholder, and I would say so.
  • Invalidation. If the underlying documents are re-ingested, every cached answer derived from them is suspect. That needs a version stamp.
3

A user asks "what about their dividends?" — nothing in that sentence says which company. How do you handle it?

Say this

This is coreference — the sentence points at something mentioned earlier instead of naming it. If you send it to retrieval as-is, you search for the word "dividends" with no company and no year, and you get noise from five companies.

There are two ways to fix it. You can ask a model to rewrite the question using the history, which handles anything but costs a call and can hallucinate a company that was never mentioned. Or you can carry forward the specific entities you care about, which is cheap and predictable but only covers the entities you listed.

I chose the cheap one, because in this domain the ambiguity is almost always the same two slots: which company, and which financial year. I scan the recent messages for a ticker and a year, and if the current question is missing one, I append what I found.

It is deliberately narrow, and I would defend that: it is deterministic, testable, adds no latency, and if it finds nothing it changes nothing.

If they dig deeper

  • Most recent wins. I scan backwards through history so that if the user switched from BHP to Rio, Rio is what gets carried forward.
  • Only fill what is missing. If the question already names a company and a year, I leave it completely alone — no state write, no risk.
  • Where it fails: "compare that with the other one" has no entity to copy, and a metric-level reference ("and the margin?") is not covered. The honest upgrade is a model rewrite with the entity carry-over kept as a deterministic backstop.
  • The enriched question is reused for the cache lookup as well, so the lookup and the later save use the same text — otherwise you save under one key and search under another and never hit.

Level 3 · In this project

Hands-on detail

Concrete files, concrete numbers. This is where you prove you built it rather than read about it.

1

Walk me through the memory layers in your chatbot.

Say this

Four layers, and every request passes through them in a fixed order before any retrieval happens.

First, durable session state. The whole graph state is checkpointed to PostgreSQL after every step, keyed by a thread id. So a conversation survives a server restart, and I can replay exactly what the system saw at each step — which is as much an audit feature as a memory feature.

Second, context compression. Once the history passes ten messages, the older ones are summarised into a single note and the last four are kept verbatim.

Third, entity carry-over. I pull company tickers and financial years out of the recent messages and top up the current question if it is missing them.

Fourth, the semantic answer cache. Past question-and-answer pairs live in their own vector collection, separate from the report chunks. Above 0.95 similarity I return the stored answer and skip retrieval entirely. Between 0.80 and 0.95 I run the full pipeline but seed it with the old answer as context.

If they dig deeper

  • Files: graph.py wires the Postgres checkpointer, nodes/compress_context.py does compression, nodes/memory.py does entity carry-over and the cache lookup, memory/semantic.py owns the cache store.
  • Numbers: compression threshold 10 messages, keep 4 recent, entity scan window 6 messages, cache tiers 0.95 and 0.80, connection pool max 10.
  • Order is deliberate: compress first so the entity scan reads a clean history, enrich second so the cache lookup uses the enriched question, cache last so a hit can skip everything downstream.
  • Two separate vector collections — reports for source documents, conclusions for cached answers — so the system can never cite its own past answer as if it were an annual report.
2

In your cache, you embed the question, not the answer. Why?

Say this

Because at lookup time all I have is a question. If the stored vector represents an answer, I am comparing a question against an answer, and those two texts look different even when they belong together.

A question is short and interrogative; an answer is long, full of numbers and hedging. Their embeddings sit in different regions, so similarity scores get noisy and unreliable.

So I store the question as the searchable content and keep the answer and its sources alongside it as metadata. That makes it question-to-question matching, which is the comparison I actually want.

If they dig deeper

  • The stored question is the enriched question, so a follow-up that was resolved to "dividends [BHP, FY2024]" is retrievable by a later user who asks the full thing directly.
  • Cached source references are stored with the answer, so a cache hit still shows the user the same citation list as a live answer. A cache hit that drops citations would look to the user like an unsourced claim.
  • The alternative — embed both and search both — doubles storage and needs a fusion rule. Not worth it until the question-to-question hit rate is measured and found lacking.
3

What stops a bad answer from being cached and served forever?

Say this

The write is gated, not automatic. An answer is only saved if two conditions hold: the quality grader passed it, and the groundedness check did not come back as "no".

Groundedness means every fact in the answer could be found in the documents that were actually retrieved. If that check fails, the answer still goes to the user — with a visible warning — but it is never written to memory.

There is one more detail I am quite pleased with. The warning text is appended after the save, so what gets cached is the clean answer. Otherwise a future user would receive a stale caveat about a verification run that has nothing to do with their question.

The general principle: the read path is optimistic, the write path is pessimistic. Getting a cache miss is cheap. Poisoning the cache is not.

This is the answer that connects all three topics The cache write is gated by the evaluator, which is also the guardrail. If they ask you to name one place where memory, evaluation and guardrails meet in your code, this is it — nodes/answer.py, the save_conclusion call guarded by grade == "pass" and grounded != "no".
If they push back on memory — gaps I would name before they find them

What it does not do today

  • No expiry and no invalidation. If the reports are re-ingested or restated, cached answers derived from the old version stay live. Nothing links a cache entry to the ingest run that produced it.
  • The cache is global. There is no per-user namespace. In a bank that is a hard blocker: one customer's answer must never be reachable by another. My project has no customer-specific data, which is why it has not bitten me — that is luck, not design.
  • Thresholds are hand-picked. 0.95 and 0.80 were chosen by judgement, not fitted on a labelled set of paraphrase pairs. I have no measured false-hit rate.
  • No hit-rate or false-hit telemetry. I cannot currently answer "how often does the cache fire, and how often is it wrong?"

What I would build first at enterprise scale

  • Namespace every entry by tenant and user, enforced at the store layer rather than in application code, so a missing filter cannot leak.
  • Version stamp plus time-to-live. Each entry records the ingest version it was derived from; a new ingest invalidates the affected entries instead of relying on age alone.
  • Tune the threshold empirically. A few hundred labelled question pairs, then pick the cut-off from the precision curve — and re-check it whenever the embedding model changes, because a model swap silently moves every score.
  • Instrument it: hit rate, tier distribution, and a sampled human review of hits, so a rising false-hit rate is visible before a customer finds it.
  • Redact before storing. Any customer-derived text should be stripped of personal data on the write path, not on the read path.

2 · Evaluation

"Rigorous evaluation strategies for probabilistic systems, including offline evals, online experimentation" — and the role explicitly shapes "the evaluation methodology" of the function.

Level 1 · Architecture

Lead / Senior

This is the section the job ad cares about most. They are hiring someone to set the standard, so answer as someone who has decided what "good" means, not someone who runs a script.

1

How do you evaluate a system that can give a different answer every time you run it?

Say this

The first move is to stop asking "is the answer correct" and start asking "how does this system behave across a fixed set of situations". One run tells you almost nothing, because the same input can produce different output.

So I fix everything I can. A fixed set of cases with known expectations. Deterministic checks wherever a check can be deterministic. And where I have to judge something subjective, I score a property of the answer rather than matching the text.

The second move is to evaluate components before the whole. If I only test end to end, a failure tells me the system is wrong but not where. In my project I can test the router alone, retrieval alone, and the grader alone, so a regression points at one component.

And then there are two phases, not one. Offline evaluation is a fixed test set before release — it stops known regressions but cannot tell you about real users. Online evaluation is measuring live traffic after release — that is where reality arrives. You need both, and the offline set should be fed from what online finds.

If they dig deeper

  • Where variance actually comes from: sampling in the model, non-determinism in the retriever's tie-breaking, changing external data, and silent provider-side model updates. Only the first is fixable by setting temperature to zero — the rest you have to measure.
  • Repeat runs are a measurement, not a nuisance. Running one case five times and looking at the spread tells you how stable the behaviour is. A case that flips between pass and fail is a finding, not a flake to retry.
  • Ship a per-dimension scorecard, never one number. A single aggregate score hides the trade where retrieval improved and refusals got worse.
2

What exactly do you measure? Give me the dimensions.

Say this

I break the pipeline into stages and measure each stage on its own terms, because "answer quality" is not one property.

Routing: did the system decide correctly whether to retrieve, to search the web, to ask a clarifying question, or to just answer. That is a classification problem with a clean label, so it is cheap and exact.

Retrieval: given a question with a known correct figure, did a document containing that figure reach the answering step, and at what rank. That is measured without any model in the loop.

Groundedness: is every claim in the answer supported by the documents that were retrieved. This is the hallucination metric and it is the one I care about most in a regulated setting.

Boundary behaviour: on a question the system should not answer, does it decline properly instead of inventing something. And alongside all of that, latency and cost per query, because an accurate answer nobody waits for is not a product.

Right for the wrong reason

End-to-end answer looks correct, but the retriever missed the source and the model answered from its training data. Passes an answer-quality check, fails groundedness, and will fail next quarter when the figures change.

Why component metrics catch it

Retrieval recall is measured separately, so "the evidence never arrived" shows up as a retrieval failure even when the final text happens to be right.

If they dig deeper

  • Refusal has two error types and you must report both: answering something it should have declined, and declining something it could have answered. Optimising one alone is how you get a useless, over-cautious assistant.
  • Citation correctness is separate from groundedness. An answer can be fully grounded but point at the wrong page — which destroys user trust faster than a hedge does.
  • For a bank I would add coverage of the refusal taxonomy itself: financial advice, personal data, and out-of-scope are three different refusals with three different required responses.
3

You change a parameter and the score goes up. How do you know it is a real improvement and not noise?

Say this

Three things: isolate the change, compare on the same cases, and be honest about how much the sample size can support.

Isolate first. If I change retrieval, I take the model out of the loop entirely and score retrieval directly. Otherwise the model's own variance is larger than the effect I am trying to see.

Then compare paired, not averaged. Same cases, both configurations, and I look at what moved per case — how many got better, how many got worse, how many were found that were previously missed. A mean can improve while individual cases regress, and those regressions are what an incident is made of.

Then I state the limit out loud. With about a dozen cases I can see direction, not significance. I would call that evidence to proceed, not proof — and if the decision were expensive, I would go and label more cases before shipping it.

Say this sentence if you say nothing else "With n around twelve I can claim direction, not significance." The job ad asks for "strong foundations in statistics… quantify uncertainty". Volunteering the limit of your own evidence is the single clearest signal that you actually have those foundations.

If they dig deeper

  • Paired tests are the right tool because the case is the unit of variation. Comparing two independent averages throws away the pairing and needs far more data for the same confidence.
  • Watch for the metric that only looks better. Widening a candidate pool almost always improves recall — the honest question is whether precision after reranking held, and whether cost and latency moved.
  • Pre-register what would make you reject the change. Deciding the threshold after seeing the numbers is how teams talk themselves into regressions.

Level 2 · Implementation

Mid-level

How the harness is actually built: what runs on every commit, what costs money, and how you keep the judge honest.

1

Offline evals, online experimentation — what is the difference, and why do you need both?

Say this

Offline means a fixed set of cases with expected outcomes, run on demand. It is repeatable, cheap, and safe, so it is the right thing to gate a release on. Its weakness is that it only contains failures you already thought of.

Online means measuring the system on real traffic. Usually that is an A/B test: some share of requests go to the new version, the rest to the current one, and you compare outcomes. Its strength is that it finds the questions you never imagined. Its weakness is that some users get the worse version while you learn.

The two connect in a loop, and that loop is the actual methodology. Online finds a new failure, that failure becomes an offline case, and the offline suite gets stronger. If your test set only grows when an engineer has an idea, it will drift away from what users do.

Before a real A/B I would run in shadow mode — the new version processes real traffic but its output is logged rather than shown. You get real distributions with zero customer risk.

If they dig deeper

  • The metric problem online is that you rarely have ground truth live. So you lean on proxies — retry rate, refusal rate, thumbs down, abandonment, escalation to a human — plus sampled human review.
  • Guardrails on the experiment itself: automatic rollback if refusal rate or latency crosses a bound, so a bad variant cannot run overnight.
  • Randomise per user, not per request, or the same person sees two different behaviours in one conversation and the experience is incoherent.
  • In a bank, an online experiment on customer-facing AI is a governance event, not just an engineering one — it needs sign-off, a stated hypothesis, and an exit rule agreed in advance.
2

You use a model to grade the model's output. How do you know the grader is right?

Say this

That is the honest weak point of LLM-as-judge, and the answer is that the judge is a component like any other, so it needs its own evaluation.

Concretely: I take a sample of cases, label them myself, and measure how often the judge agrees with me. If agreement is poor, the judge's verdicts are not evidence and I should not be shipping on them.

Then I make the judge's job as narrow as possible. It never scores "quality" on a scale. It gets the question, the retrieved documents, and the draft answer, and it returns a strict structure — pass or fail, plus a list of claims it could not verify. A constrained output is far more stable than a free-text opinion.

Two more things that matter. I tell it explicitly which differences do not count as unsupported — different units, rounding, accounting notation — because otherwise a correctly grounded answer gets flagged constantly. And I set its strictness deliberately: for pass-or-fail I told it to be lenient, because that gate only exists to catch answers that are empty of content.

If they dig deeper

  • Known judge biases: preference for longer answers, preference for its own writing style, and position effects when comparing two candidates. Position bias you can partly cancel by swapping the order and re-asking.
  • Split the two tasks in the prompt. "Is this answer useful" and "is every claim supported" are different judgements; asking for both in one unstructured response makes each worse.
  • Deterministic checks first, always. Anything a regular expression or a numeric comparison can verify should never go to a model — cheaper, faster, and it cannot change its mind.
  • The judge is a dependency you do not control. If the provider updates the model, your grading baseline moves. That argues for pinning versions and re-running the agreement check on every model change.
3

How do you put evaluation into CI without a huge bill?

Say this

By splitting it into tiers by cost, and running each tier at the frequency it can afford.

The cheap tier calls no external service at all. It feeds synthetic outputs through the real evaluator code. That does not prove the system works — it proves my cases are well-formed and my scoring logic is correct. It is free and fast, so it can run on every commit.

The middle tier runs the real components, but only a handful of cases, and only the cheapest path that is still meaningful. For routing I run the routing nodes alone rather than the whole graph. That is a smoke test on a pull request.

The expensive tier is the full suite against real services and real indexes. That runs on a schedule, not on every push, and its results are compared against a stored baseline so a drop is visible.

The rule I would set as a standard: the release gate must be deterministic. If a build can fail because a model felt different today, engineers will learn to re-run until green, and the gate stops meaning anything.

Mock tier — every commit

No network, no model. Validates case schema and evaluator logic. Fails loudly and deterministically. Cost: zero.

Real tier — PR smoke, nightly full

Actual models and indexes, limited case count on PRs and the full suite on a schedule, compared to a committed baseline.

If they dig deeper

  • Cache model responses by input hash so repeated CI runs of an unchanged case are free, and invalidate the cache when the prompt or model version changes.
  • Commit the baseline scores to the repository. A number in a log nobody reads is not a regression gate; a number in a file that a diff changes is.
  • Cases need an owner and a reason. Every case in my suite carries a why_it_matters field, and the loader rejects a case without one, so nobody can add a case whose purpose is unexplained.

Level 3 · In this project

Hands-on detail

One experiment told properly is worth more than a list of features. The BM25 story below is your strongest single answer in the whole interview — practise it until it is smooth.

1

What does your eval suite actually cover?

Say this

Four suites plus a separate retrieval harness. Routing, retrieval, calculation, and boundary behaviour, with about forty cases in total as line-delimited JSON files.

Every case has an id, the query, the expected outcome, and a why_it_matters line explaining what regression it would catch. The loader refuses to run a case that is missing any of those, so the suite cannot quietly fill up with cases nobody understands.

There are two run modes behind one command line. Mock mode calls nothing external and checks that my cases and evaluators are correct. Real mode runs the actual nodes, and I can scope it down to one suite, a case count, or a single case id, which is what keeps real-mode cost controllable while debugging.

One design choice I would highlight: real mode runs the cheapest path that is still meaningful. Routing cases execute the routing and query-rewrite nodes directly rather than the full graph, so a routing regression is caught without paying for retrieval and synthesis.

If they dig deeper

  • Suite sizes: routing 8, retrieval 8, calculation 6, boundary 6, plus 12 numeric ground-truth cases used by the A/B harness. Small, and I would say so unprompted.
  • Boundary cases are the interesting ones: the empty query must not trigger retrieval at all, and "what were the numbers last year?" must produce a clarifying question rather than a guess.
  • Adding a suite is one registry entry mapping a name to a case-loader and an evaluator, so the shape of the harness does not resist new dimensions.
  • Failures print per case with the reason, and the process exits non-zero if anything failed — so it is already shaped to be a CI gate even though it is not yet wired into one.
2

Tell me about an experiment you ran where the result surprised you.

Say this

My keyword retriever was matching words literally, so "Revenue" with a capital R and "revenue" were two unrelated terms, and "revenue," with a comma was a third. I wrote a proper tokeniser to normalise them.

While reading the library source I found something I did not expect. BM25 is supposed to handle common words automatically: a word appearing in most documents gets a low weight, so "the" should be nearly worthless. The formula can produce a negative weight for such words, and this library refuses negative weights — it clamps them up to a floor instead.

The result inverted the intent. "The" appeared in 84 per cent of my chunks, so its true weight was negative, and the floor lifted it to 1.81 — higher than "Group" at 1.54, which is a genuinely informative term. Only eight words were affected, but those eight appear in nearly every natural-language question.

I measured it. For the query "what was the revenue of the group in 2024", 59 per cent of the top document's score came from "what", "was", "the", "of" and "in". After removing those words, the new top five documents had zero overlap with the old top five. That is not a nudge in the ranking — the results had been decided by filler words.

The lesson I took is the one I would bring to a team: the textbook claim was right about the algorithm and wrong about the implementation, and only reading the source and measuring the actual scores showed the difference.

Why this answer works It shows source-level debugging, a quantified before-and-after, and a conclusion that generalises. It also answers "conduct rapid experimentation… what works, what fails, and why" from the job ad without you having to claim it.

If they dig deeper

  • Normalisation numbers: documents matching "revenue" went from 221 to 278, up 26 per cent; vocabulary shrank from 37,933 to 23,676, down 37.6 per cent. The point of the smaller vocabulary is not memory — it is that the document frequencies became correct, so inflated weights fell back to their true level.
  • Financial text needs care: I strip punctuation only from the start and end of a token, so $1.2B and 16% survive intact while revenue, and (Group). get cleaned. Numbers with thousands separators are indexed twice, as 53,658 and 53658, so a user typing either form matches.
  • The bug my own change introduced: a question made entirely of common words, like "what is it", tokenised to nothing. An empty query scores every document at zero, and the library then returns an arbitrary twenty documents with no error at all. I made the tokeniser fall back to the unfiltered tokens when filtering would empty the list, and asserted it in a self-check. Silent failure is worse than an exception.
  • Deployment consequence: the tokeniser is stored inside the index file as a function reference, so the module path cannot be renamed without invalidating every built index. I documented that at the top of the file because it is the kind of constraint a future maintainer breaks by accident.
3

How did you measure the retrieval change itself?

Say this

I built a small A/B harness that calls the retrieval node directly and never touches the language model, so the only thing that differs between the two runs is the retrieval configuration.

The ground truth is exact figure strings that I verified exist in the ingested text — for example 55,658 for BHP's FY2024 revenue and 29,016 for its underlying EBITDA. A case scores as a hit if a returned document contains the figure, and I record the rank at which it first appears.

I report hit count within the final cut, hit count within the top five, and mean rank of the hits. Then per case I print whether it improved, regressed, was newly found, or was newly lost — because the aggregate can improve while individual cases get worse, and the regressions are what I actually want to see.

The two configurations were the old one — a candidate pool of eight, no reranking — against the new one, a pool of twenty followed by a cross-encoder rerank down to eight.

One practical detail: the reranker is a paid API with a token-per-minute limit, so the harness paces itself with a fixed pause between cases. Without that, the comparison fails for rate-limit reasons and looks like a quality result.

If they dig deeper

  • Why widen then narrow: the fusion step ranks by position and never reads the text, so a chunk holding the exact figure can sit below topically similar boilerplate. A wider pool raises the ceiling of what can be found; the cross-encoder then reads each candidate against the question and decides which eight survive. Cost downstream is unchanged because the final count is the same.
  • Exact string matching as ground truth is deliberate. It is strict and it cannot be argued with. It also under-counts a document that expresses the same figure differently — a known limitation of the metric, not of the retriever.
  • A cache had to be cleared between configurations, because the candidate count is baked into the loaded index object. Missing that would have made the second run silently reuse the first configuration — the sort of bug that produces a confident, wrong conclusion.
  • Honest limit: twelve cases, single run each. Direction, not significance.
If they push back on evaluation — gaps I would name before they find them

What it does not do today

  • Evals are not in CI. The only automated workflow in the repository is deployment. The suite is shaped to be a gate — per-case reporting, non-zero exit — but nothing runs it automatically, so nothing stops a regression reaching main.
  • No stored baseline. Scores print to a console. There is no committed file to diff, so "did this get worse" relies on somebody remembering last week's number.
  • The judge is unvalidated. I have never measured how often the groundedness grader agrees with a human on the same cases, so I cannot state its error rate.
  • No online measurement at all. No A/B, no shadow mode, no drift detection, no sampled human review of live answers.
  • Small n. Around forty offline cases and twelve numeric ones. Enough to catch a gross regression, not enough to make a statistical claim.

What I would build first at enterprise scale

  • Wire the tiers into CI immediately: mock suite on every commit, limited real smoke on pull requests, full real suite nightly against a committed baseline. This is a day of work and it is the highest-value gap on the list.
  • Grow the golden set from production traffic. Sample real questions, especially ones that were retried, refused, or thumbs-downed, label them, and promote them into the offline suite. That is how the test set stops drifting away from reality.
  • Run a judge agreement study on a few hundred human-labelled examples, publish the agreement rate, and re-run it whenever the judge model or prompt changes.
  • Drift monitors on distributions, not just averages: retrieval score distribution, refusal rate, groundedness rate, answer length, tool-call mix. A shift in shape is the earliest signal that something upstream changed.
  • Shadow mode before any customer-facing A/B, with pre-agreed stopping rules and automatic rollback on a refusal-rate or latency breach.

3 · Guardrails

"Explainable, auditable, and aligned with NAB's Responsible AI principles" — plus observability, drift detection and incident classification.

Level 1 · Architecture

Lead / Senior

A bank is not asking whether you know the word "guardrail". They are asking whether you can decide, layer by layer, what happens when a check itself fails.

1

How would you design guardrails for a customer-facing LLM product in a bank?

Say this

As layers at boundaries, not as one filter at the end. A single check before the answer is the design people reach for first, and it is too late — by then you have already paid for the work and you have nothing left but to block.

I put a check at each boundary the request crosses. At the input: is this question in scope, and is it clear enough to attempt. At retrieval: is what came back actually relevant, and did I find something for everything the user asked about. At generation: is every claim in this draft supported by the evidence. At output: is there a substantive answer here at all, and do the citations resolve.

Two rules I hold across all of them. Cheap and deterministic before expensive and probabilistic — if a length check or a lookup can catch it, do not spend a model call. And every layer states its verdict into the run state, so the decision is recorded rather than implied by which branch the code took. That recorded trail is what makes the system auditable.

One filter at the end

Full pipeline runs, then a final check blocks the answer. Every failure costs the entire compute budget, and the only available response is to refuse. No diagnosis of where it went wrong.

Layered at boundaries

Each stage validates before handing on. Failures are cheap, localised, and named — so the system can ask, retry, degrade, or refuse, and the log says which layer fired.

If they dig deeper

  • Four responses, not one. Refuse, ask a clarifying question, answer with a visible caveat, or retry with a better query. Treating every guardrail failure as a refusal is how you build something users route around.
  • Separate the policy from the mechanism. What counts as out of scope is a business and risk decision that should be reviewable by non-engineers; how it is enforced is mine. If those are tangled in a prompt, risk cannot audit it.
  • Name the classes of harm you are guarding against — a wrong figure, unlicensed financial advice, leaked personal data, an out-of-scope answer. They need different controls, and a generic "safety check" covers none of them properly.
2

Your guardrail itself throws an exception. Do you fail open or fail closed?

Say this

It depends on the layer, and the important thing is that I decided it per layer, deliberately, rather than letting the exception handler decide.

My rule is this. If the layer is a precision improvement — it makes a good answer better — it fails open, because losing it degrades quality but blocking on it takes the whole service down. If the layer is a correctness floor — without it a wrong or empty answer reaches the user — it fails closed.

So in my project the two model-based judges both fail open. If the relevance grader errors, I pass all the retrieved documents through instead of none. If the groundedness grader errors, I mark the answer as passing rather than blocking it. Both are optional precision layers sitting on top of retrieval that already worked.

The final check is different. It is deterministic, it makes no network call, so it has essentially nothing to fail on — and it fails closed by construction: no substantive content means the fallback path, always.

What I would add in a bank is that fail-open must be loud. Silently degrading is how a system runs for a week with its safety layer switched off. Every fail-open needs a metric and an alert.

This is a senior-level answer and interviewers listen for it Most candidates answer "fail closed, safety first" and stop. Naming a rule, applying it differently per layer, and then adding "fail-open must be instrumented" is what separates someone who has operated a system from someone who has only built one.
3

How do you make an LLM system explainable and auditable?

Say this

I would separate three things that get bundled together: can the user see why, can an engineer reconstruct what happened, and can a reviewer prove it months later.

For the user, explanation has to be in the answer itself. Every figure drawn from a report carries a citation marker that resolves to a company, a financial year and a page — so the claim can be checked against the source. And when the verification step could not confirm something, the answer says so, in the answer, not in a log.

For the engineer, every decision is written into the run state and the state is checkpointed at every step. So I can reconstruct not just the final answer but which route was chosen, which documents survived grading, what the judge said. Reconstructable is stronger than logged, because logs are prose and state is structured.

For the reviewer, the gap in my project is that this trail is operational storage, not an audit record. It is not immutable and it has no retention policy. In a regulated environment I would write the decision trail to append-only storage with a defined retention period, and treat it as a different artefact from the conversation state.

If they dig deeper

  • Confidence must be earned, not decorative. Mine is derived from the groundedness verdict — all claims supported, some unsupported, or many — so it maps to a check that ran, rather than to a model's self-reported certainty. Asking a model how confident it is produces a number with no referent.
  • Uncited sources are removed and the numbering is rebuilt before the answer is shown, so the reference list never contains something the answer did not use, and the markers are always contiguous.
  • Incident classification needs a taxonomy up front: wrong figure, unsupported claim, wrong refusal, leaked data, unavailable. Without categories agreed in advance, every incident is a one-off and nothing gets counted.

Level 2 · Implementation

Mid-level

The mechanics: how you detect a hallucination, how you choose between refusing and hedging, and how you keep a retry loop from running away.

1

How do you stop the model from hallucinating?

Say this

I would push back gently on the word "stop". A language model predicts plausible text; producing a confident wrong number is not a bug you can patch out. So the goal is not prevention, it is detection and containment.

Detection works because in a retrieval system I have something to check against. I pass the draft answer and the exact documents it was given to a verification step, and ask a narrow question: which claims in this answer cannot be found in these documents.

Containment is what happens next, and it has three settings. If a lot is unsupported, I retry retrieval targeted at the specific claims that failed. If a little is unsupported, the answer still goes out, but the user sees which claims are unverified. If nothing is unsupported, it goes out clean.

One refinement worth mentioning: the verifier also classifies why a claim is unsupported. Was it arithmetic derived from figures that were present, was it estimated from a trend, or does it have no connection to the documents at all. The first is often legitimate and just needs to be shown as a derivation. The last is the real hallucination. Treating those three identically loses information the user needs.

If they dig deeper

  • The verifier needs help to avoid false alarms. Financial tables put the unit in a header row and the number in a data row, so the raw text says "29,016" and the answer says "US$29,016 million". Without telling the verifier how to match those, it flags correct answers constantly, and a warning that fires on correct answers trains users to ignore warnings.
  • Tolerances have to be explicit: rounding, scale prefixes, bracket notation for negatives, metric-name variants, and different ways of writing a financial year all count as supported.
  • Grounded is not the same as correct. A claim can match a document perfectly and still be wrong, if the document was misread at ingest. This check bounds invention, not truth — and I would say that plainly rather than overselling it.
2

When should the system refuse, when should it ask, and when should it answer with a caveat?

Say this

Three different situations, and mixing them up is a product failure rather than a technical one.

Refuse when the request is outside what the system is for. Somebody asking for a poem from a financial report assistant should get a clear boundary and a statement of what it does cover — not a poem, and not a hedge.

Ask when the request is in scope but underspecified. "What were the numbers last year" is a reasonable question with a missing subject. Guessing produces a confident answer about the wrong company; asking one short question costs a turn and gets it right.

Answer with a caveat when I have real evidence but could not verify all of it. Refusing there throws away a mostly useful answer; hiding the gap is dishonest. Showing the answer and naming the unverified parts respects the user as someone who can decide.

And the caveat has to be specific to be useful. "This may be inaccurate" on every answer is noise. "This figure could not be verified against the retrieved documents" on the one claim that failed is information.

If they dig deeper

  • A refusal must be actionable. Mine lists the companies, the financial years and the metrics that are covered, and shows two example questions. A bare "I cannot help with that" makes the user guess.
  • Asking has a cost too. A system that clarifies too often is exhausting, so the instruction is to ask only when retrieval genuinely cannot proceed — and to attempt the question when it is merely imperfect.
  • Partial coverage deserves its own handling. If someone asks about five companies and I found four, the answer should say which one is missing rather than quietly answering about four.
3

Your system retries when a check fails. How do you stop that loop from running away?

Say this

Three constraints. A hard cap, a changed input, and a defined outcome when the retries are exhausted.

The cap is the easy part — a counter in the state, and no more than two retries. Because the state is the same object the whole graph reads, there is one counter, not one per branch.

The second is the one people miss. A retry that sends the same input gets the same answer and has only spent money. So every retry has to change something. In my case the retry is targeted: the claims that failed verification become the new search terms, and the companies whose claims failed are marked as not-yet-found so the next round searches specifically for them.

The third is that running out of retries is a normal outcome with a defined path, not an error. It goes to the fallback response, which explains what the system covers. An unbounded loop and an unhandled exhaustion are the same bug seen from two ends.

If they dig deeper

  • Retry triggers are explicit: the answer graded as failing, some requested company having no supporting documents, or two or more unsupported claims. One unsupported claim is not worth a retry — it is worth a caveat.
  • Cost per retry compounds across a fan-out. Five companies searched in parallel, twice over, is ten retrieval calls plus reranking. The cap is a budget decision as much as a correctness one.
  • In production I would emit a metric per retry reason, because a rising retry rate for one reason is an early signal that retrieval quality has drifted — before any user complains.

Level 3 · In this project

Hands-on detail

Name the layers in order. Then give the one detail that shows you thought about it harder than the job required.

1

Walk me through the guard chain in your application.

Say this

Six checkpoints, in the order a request meets them.

One, scope. The routing step classifies the question, and anything unrelated to the five companies gets a fixed reply that states what the system covers. Two, clarity. If the question is too vague to retrieve for, the graph asks a clarifying question instead of guessing.

Three, document relevance. Retrieved chunks are graded, and things like governance boilerplate or auditor's reports are dropped unless the question was about them. This step also corrects the per-company found-or-not flags, because a company is only really found if it has a document that survived grading.

Four, groundedness. The draft answer is checked claim by claim against the documents it was given, which produces the unsupported list and the retry signal.

Five, the final gate. A deterministic check that there is substantive content at all — and it sits on every path to the answer, including the direct-answer path that never touched retrieval. Failure routes to a fallback that explains the coverage.

Six, output shaping. Uncited sources are removed and markers renumbered, and a confidence level derived from the groundedness verdict is attached, with a named caveat when it is not high.

If they dig deeper

  • Files in order: nodes/retrieve_decision.py, nodes/clarify.py, retrieval_nodes/grade_docs.py, retrieval_nodes/grade_answer.py, nodes/guardrails.py with nodes/fallback.py, and nodes/answer.py with utils/citation.py.
  • Why the final gate covers every path: the direct-answer route skips retrieval entirely, so without a shared gate an empty direct answer would reach the user unchecked. Putting the check on the boundary rather than in each branch means a new branch inherits it automatically.
  • The fallback message is diagnostic. It distinguishes "nothing was retrieved" from "documents were retrieved but could not be synthesised", because those are different failures and the second one means retrieval is fine and synthesis is not.
2

Give me one guardrail decision in your code that you would defend in a design review.

Say this

When the verifier reports an unsupported claim, it is instructed to write a short label of at most eight words that names the metric and the context — for example "RIO FY2025 revenue" — and explicitly not to reproduce the sentence or the number.

The reason is that the label is shown to the user in the warning. If the verifier echoed the full claim, the warning that exists to flag a possibly invented figure would print that figure again, in the answer, as text. The safety message would become another place the wrong number appears.

A user skimming might read the number and miss the framing. So the warning says which claim to distrust without restating the value they should not rely on.

It is a small thing, and it is the kind of thing I look for: not whether the check works, but what the check's own output does when it reaches a human.

Have this one ready verbatim It is the strongest guardrail answer you have, because it is a decision nobody asked you to make. That is the difference between implementing a requirement and owning a surface.
3

Where does your system deliberately fail open, and can you justify each one?

Say this

Three places, and each was a decision rather than an accident.

The relevance grader fails open: if that model call errors, I pass all retrieved documents through rather than none. Losing the filter means a noisier context. Failing closed would mean no context at all, which turns a degraded answer into no answer.

The groundedness grader also fails open: on error the answer is marked as passing. That is the one I am least comfortable with, because it is the hallucination check, and its failure is silent. What makes it defensible is that the deterministic final gate still runs, and what would make it acceptable in production is an alert on the error rate — which I do not have yet.

The reranker fails open too: on a network error or a rate limit, the results keep the ordering from the fusion step. It is a precision improvement on a ranking that already exists, so degrading to the previous ordering is exactly right.

And one that fails closed on purpose: the final content gate is deterministic with no network dependency, so an empty answer always routes to the fallback. The floor is the layer that must not have an availability failure mode, which is precisely why it does not call anything.

If they dig deeper

  • The routing step fails safe rather than open or closed: if the classifier errors, the default is to retrieve and never to block on clarification. Retrieving unnecessarily costs money; refusing a valid question costs trust.
  • Silent fail-open is the real risk, not fail-open itself. Every one of these swallows an exception. In production each needs a counter and a threshold alert, otherwise the system can run with a safety layer effectively switched off and look completely healthy.
  • How I would express this to a risk function: a table of layer, failure mode, direction, blast radius, and the alert that covers it. That table is the artefact, not the code.
If they push back on guardrails — gaps I would name before they find them

What it does not do today

  • The final gate is a length check. Ten characters of content. It is a floor against empty and degenerate output, not a content filter — and I would rather say that than let them discover it. The real verification work happens one layer earlier, in the groundedness check.
  • No prompt-injection defence. Text extracted from PDFs is placed next to my instructions. A document containing instruction-like text is currently trusted input. For public filings the risk is low; for any document a customer can supply it is not.
  • No personal-data or toxicity handling. No detection, no redaction before logging. The project has no customer data, so this never surfaced — but at NAB it is table stakes, not a nice-to-have.
  • Scope enforcement rests on one model call with no deterministic backstop, so a classification error is unguarded.
  • No rate limiting and no immutable audit log. State is checkpointed for operations, not retained for review.

What I would build first at enterprise scale

  • Make the final gate a real policy layer: required-citation check when the answer states figures, numeric sanity bounds, refusal-taxonomy classification — with the length check kept as the cheapest first test.
  • Treat retrieved text as untrusted. Scan at ingest, fence document content clearly from instructions in the prompt, and never let retrieved text reach a tool-calling path unfiltered.
  • Redact personal data on the write path — before logging, before caching, before tracing — so no downstream system has to remember to.
  • Instrument every fail-open with a counter and an alert threshold, and surface them on one dashboard, so "the safety layer has been erroring for a week" is impossible to miss.
  • Agree an incident taxonomy with risk up front — wrong figure, unsupported claim, wrong refusal, data exposure, unavailable — with severity and routing per class. Categories agreed before the first incident, not after.
  • Write the decision trail to append-only storage with a retention policy, separate from conversation state, so a reviewer can reconstruct any answer months later.

Numbers to remember

Specifics are what make an answer credible. These are the ones worth having on the tip of your tongue.

System

  • 5 companies — BHP, RIO, FMG, MIN, NST — across FY2023–FY2025
  • Candidate pools 20 + 20, fused, reranked down to 8 final documents
  • Fusion weights 0.45 keyword / 0.55 vector
  • Cache tiers 0.95 direct hit, 0.80 context seed
  • Compression at 10 messages, keep 4 verbatim, entity scan 6 messages
  • Retry cap 2, per-company parallel fan-out

The BM25 experiment

  • "the" appeared in 84% of chunks; true weight negative, clamped up to 1.811 — above "Group" at 1.542
  • Only 8 words affected, but they appear in nearly every question
  • 59% of the top document's score came from filler words
  • Top-5 overlap before versus after: 0 of 5
  • Documents matching "revenue": 221 → 278 (+26%)
  • Vocabulary: 37,933 → 23,676 (−37.6%)
  • Evidence base: 12 numeric cases — direction, not significance
If you forget every number, remember this shape Problem → the naive approach and why it fails → what I built → the number that shows it worked → what it still does not cover. Every strong answer on this page follows that shape, and you can improvise inside it for a question you have not prepared.

Glossary

Every term used above, in plain words. If an interviewer uses one you are unsure of, it is always fine to say "do you mean X?" — checking reads as precise, not weak.

Unfold all definitions — 39 terms, plain-English
LLM — large language model
A model that predicts likely next text. It has no notion of true or false; it produces what looks plausible, which is why everything else on this page exists.
Agent / agentic system
An LLM that can take actions in a loop — search, call a tool, check its own output — rather than answering in one shot.
RAG — retrieval-augmented generation
Find relevant documents first, then let the model answer using only those documents. It swaps "recall from training" for "read this evidence".
Embedding
A list of numbers representing the meaning of a piece of text. Similar meaning gives numerically nearby lists, which is what makes meaning-based search possible.
Vector store / vector database
Storage that finds the nearest embeddings to a query embedding quickly. Mine is ChromaDB, on local disk.
Chunk
A document is too big to embed as one unit, so it is cut into pieces. Each piece is a chunk, and retrieval returns chunks, not documents.
BM25
A classic keyword ranking formula. It rewards a query word appearing often in a document, and rewards words that are rare across the whole collection.
df / idf — document frequency, inverse document frequency
How many documents contain a word, and the weight derived from it. Rare words score high, common words low. The clamping of that weight is the bug in the story above.
Tokenise
Split text into the units that get indexed and matched. "Revenue," becoming revenue is a tokeniser decision.
Stop words
Very common words — the, of, is, what — that usually carry no search signal.
Hybrid retrieval
Running keyword search and meaning-based search together. Keyword search nails exact terms like ticker codes; vector search catches paraphrase.
RRF — reciprocal rank fusion
Merging two ranked lists using each item's position, not its score. It never reads the text, which is exactly its limitation.
Cross-encoder / reranker
A model that reads a question and a document together and scores how well they match. Much more accurate than comparing embeddings, and far too slow to run over a whole collection — so it only reranks a shortlist.
recall@k / hit@k
Of the questions where a correct document exists, how often does one appear in the top k results. Measures whether the evidence arrived at all.
Precision
Of what was returned, how much was actually relevant. Recall and precision pull against each other, which is why the pool is widened and then reranked.
Context window
The maximum text a model can read in one call. A hard ceiling, and everything in it costs money and latency.
Coreference
A phrase that points at something named earlier — "their dividends", "that one". Retrieval cannot resolve it, so the pipeline must.
Semantic cache
Reusing a past answer when the new question means the same thing, judged by embedding similarity rather than exact text match.
TTL — time to live
How long a stored item stays valid before it must be recomputed or discarded.
Checkpointer
Saves the full working state after each step, so a run can survive a restart and can be replayed exactly. Mine writes to PostgreSQL.
State machine / graph
The pipeline as named steps with explicit transitions, rather than nested function calls. It makes the routing decisions inspectable and testable.
Fan-out
Splitting one request into parallel branches — here, one retrieval branch per company — then merging the results.
Hallucination
A fluent, confident statement that is not supported by any source. The failure mode that matters most in finance.
Groundedness
Whether every claim in an answer can be found in the documents that were retrieved. Note: grounded means "not invented", not "true".
LLM-as-judge
Using a model to score another model's output. Scalable, and biased in known ways, so the judge needs its own validation against human labels.
Structured output
Forcing the model to return a fixed shape — specific fields, specific allowed values — instead of free text, so downstream code can rely on it.
Guardrail
A check that constrains what the system may accept, produce, or say. Best built as layers at boundaries rather than one filter at the end.
Fail open / fail closed
When a check itself errors: fail open lets the request through, fail closed blocks it. The right choice differs per layer and should be written down.
Offline eval
A fixed set of test cases with expected outcomes. Repeatable and safe; only contains failures somebody already imagined.
Online eval / A/B test
Measuring on live traffic, usually by sending some share of requests to a new version and comparing. Finds real failures, at real user risk.
Shadow mode
The new version processes real traffic but its output is logged instead of shown. Real distributions, no customer exposure.
Canary release
Ship to a small slice of traffic first, watch the metrics, then widen or roll back.
Golden set
The curated, labelled cases you gate releases on. It should grow from real traffic, not only from engineers' imagination.
Drift
Behaviour changing over time without any code change — new kinds of questions, re-ingested documents, a silently updated provider model.
Observability / tracing
Being able to see what happened inside one request: every step, prompt, retrieved document and decision. LangSmith is the tracing tool wired into this project.
CI/CD — continuous integration and delivery
Automation that runs checks on every change and deploys what passes. For AI systems the gate must be deterministic, or it gets ignored.
Prompt injection
Text inside a retrieved document that tries to act as an instruction to the model. It matters because retrieved content sits right next to your own instructions.
PII — personally identifiable information
Data that identifies a person. In a bank it must be detected and redacted before it reaches logs, caches or traces.
n
The number of cases behind a measurement. Saying yours out loud is how you show you know what it can and cannot support.