Deployed · Internal application

Sales Intelligence

Cited answers, or a clear “not covered”.

Reps were answering prospect questions from memory and old decks. This answers from the approved library, cites the row it used, and says plainly when the library does not cover the question.

  1. 210+

    Questions answered

  2. 40

    People using it

In the first month after launch
Launched early October 2026

Try a sample answer Read the case study See the RFP platform behind it

Ask about Ben

This is not the deployed assistant. It runs the same stages in your browser over a sample library about me — no model call, no company system, no customer data. The one that sales uses runs inside the internal application on the approved library.

Ask “What's Ben's favorite movie?” to see what it does with a question the library does not cover: it says so rather than answering. “Does Ben have a cat?” shows it rejecting a source that does not support the claim. Either way, every citation opens the record behind it.

Browser-based demo. Responses use local sample data; Claude and company systems are not connected.

Project details

My role

I built the retrieval, the citation checks and the access rules — Python on AWS Lambda and DynamoDB, answering with Claude on Amazon Bedrock over documents in Amazon S3. It runs on the answer library and search index I had already built for the RFP module, not a second copy of either.

Beyond the build itself: the prompt and tool contract are mine, and so is the single module that decides which library rows a salesperson may reach. It runs inside the same internal application as the RFP module, whose shared framework a colleague owns and maintains. The employer is a Human Capital Management B2B SaaS company; it and my colleagues are not named here.

Project status

Deployed into the internal application in the first week of October 2026 and in use by sales since. The counts above are a usage snapshot: they do not establish how accurate the answers were, or any time saved.

Validation and scope

  • It answers only from library entries. An answer left without a valid citation is replaced with "the library doesn't cover this" rather than shown.
  • Client reference contact details are never sent to the model. The entry says permission must be confirmed with the account owner before each use.
  • The citation check establishes provenance, not truth. It confirms each ID shown was retrieved that turn and names a real entry; it does not verify the claim or make the library complete.
  • The demonstration's retrieval and checks are JavaScript standing in for the Python: the same stages in the same order, and simpler.

Built with

  • Python
  • AWS Lambda
  • DynamoDB
  • Amazon S3
  • Claude on Amazon Bedrock
  • Claude Code plugin

What the real assistant does

The deployed assistant answers prospect questions for the sales team, from the same Master Answer Library the RFP Response Platform already uses — one library, two modules, so an approved change shows up in both at once. It runs inside the company’s internal application. The demonstration above is not it: that one runs in your browser over a sample library about me, and calls no model and no company system.

What comes back alongside each answer is what lets a salesperson judge it before reusing it: the source record behind every citation, with its ID, category, status and date; both sides of any two entries that disagree; and an explicit list of what the library does not settle — pricing to confirm for this deal, an entry marked out of date, a reference whose permission the account owner has to clear first.

Key engineering decisions

Pick one to read it. The constraint, the implementation and the cost are behind each.

Choose an engineering decision to read

The library is searched before the model is asked anything

The model never starts from a blank context: it is handed the entries the search found, and the only others it can reach are ones it asks for by name.

The constraint, what I built, and what it cost
The constraint
A model asked a question cold will answer it. The answer has to come from approved library entries, not from whatever the model happens to know.
What I implemented
Every turn searches the library with the question first and puts the matching entries into the request. The model may call `search_library` at most twice more, and each call widens the set of entries it is allowed to cite. A follow-up also carries the previous question and the rows the last answer cited, so "and what about the turnaround?" still has something to resolve against.
What it cost
A question the search misses gets a worse answer than a guessing model would give, and every turn costs a search before it costs a token. That is the trade: the assistant can only be as good as the library, and in exchange every answer arrives with the source records and review flags a salesperson needs to judge it before reusing it.

Instead ofa single prompt carrying the whole library, or a model free to answer from general knowledge.

The checks run on the answer, not in the instructions

Whatever the model wrote, code removes citations that were not retrieved and refuses to show an answer claiming support it does not have.

The constraint, what I built, and what it cost
The constraint
An instruction not to invent a source is a request. Server-side checks enforce the citation rules after generation.
What I implemented
Each citation is matched against the entries retrieved that turn and anything else is stripped. An answer that claims support but is left with no valid citation becomes "the library doesn't cover this", and its unsupported text is dropped rather than shown. The passage under each citation is found in the entry's own text by matching words, so it cannot be paraphrased into something the entry never said.
What it cost
A correct answer with a mistyped ID is thrown out along with the invented ones, and the passage shown is sometimes not the sentence a person would have chosen. Both are better than a claim with a source that does not exist.

Instead oftrusting the system prompt, and checking citations by reading them.

What sales may see is settled before anything is searched

The rules about which rows a salesperson can reach are applied once, ahead of search, display and the model call.

The constraint, what I built, and what it cost
The constraint
One library serves proposal work and sales conversations, and the two audiences may not see the same rows.
What I implemented
A single module decides what is listed: rows with no approved answer yet are left out, conflict records appear as notices on the rows they concern rather than as answers in their own right, and reference contact details are withheld from the model entirely. Nothing downstream gets to re-decide it.
What it cost
A new kind of row has to be classified there before it can appear anywhere, which is a step. In return there is one file to read to know what a salesperson can and cannot reach.

Instead offiltering at each call site, where the search, the chat and the library page would each end up with their own idea of what is visible.