DEV Community

Cover image for I Keyed Hindsight Memory by Vendor, Not by Employee
Neha Reddy
Neha Reddy

Posted on Fully Autonomous

I Keyed Hindsight Memory by Vendor, Not by Employee

Six months after our IT team squeezed a 20% discount out of Microsoft, someone in Finance sat down with the same account manager and opened with a lowball offer. The negotiation stalled for a week. Nobody in Finance knew that IT had already learned that exact tactic doesn't work.

The knowledge existed. It just lived in one person's head, and that person was in a different department.

That's the problem I built Leverage to fix, and it changed how I think about agent memory. Almost every memory setup I see is personal: an assistant remembers user A's history with user A. That model breaks the moment the valuable knowledge belongs to a relationship rather than a person. So I flipped the key. Memory in Leverage is stored per vendor, not per employee, on top of Hindsight, an open-source agent memory system.

What the system does

Leverage has two jobs:

  1. Log a negotiation. Any employee records how a deal went: discount, payment terms, what the vendor bent on, what it refused, which tactic worked or failed.
  2. Get a brief. Before a meeting, anyone (including a new hire who has never spoken to that vendor) opens the vendor page and gets a synthesized brief built from everyone else's deals.

The stack is a Next.js 15 frontend and a FastAPI backend. Hindsight does the memory work through its three primitives: retain (write), recall (search), and reflect (synthesize). There's one write path and one read path, and both go through the same shared bank per vendor.

Procurement ─┐
IT           ─┤
Sales-Ops    ─┼─►  Hindsight bank "vendor-microsoft"  ─► reflect() ─► Brief
Finance      ─┤
New hire     ─┘  (reads the same bank)
Enter fullscreen mode Exit fullscreen mode

Leverage system architecture

Where Hindsight sits: one shared bank per vendor between the FastAPI backend and a small SQLite index.

Sign in/Sign up page

Vendor list with confidence badges

Each vendor card shows its Hindsight bank id and a confidence badge based on how many deals are recorded.

The through-line: one line of code that decides everything

The whole design rests on a function that is almost embarrassingly small:

def vendor_bank_id(vendor: str) -> str:
    clean_vendor = vendor.strip() if vendor else "unknown"
    return f"vendor-{slugify(clean_vendor)}"
Enter fullscreen mode Exit fullscreen mode

"Microsoft", " microsoft " and "MICROSOFT" all resolve to vendor-microsoft. Every employee writes into that bank and reads from that bank. There is no user id anywhere in the memory key. The person who logged the deal is stored as content inside the memory, not as the address of it.

That's the entire inversion. Per-user memory answers "what did I tell you?" Per-entity memory answers "what does the organization know about this vendor?" For anything collaborative, the second question is the one that saves money.

Writing memories Hindsight can actually use

Hindsight extracts facts, entities and time from what you retain, so the quality of what goes in matters. The form gives me structured fields, but I don't retain JSON. I turn each deal into plain, explicit sentences.

Log negotiation form

Any employee logs a deal here, and it's retained into the vendor's shared bank.

Terms and tactics fields

Flexible terms, firm terms and the tactic used become facts Hindsight can reason over.

lines = [
    f"Negotiation Record: On {date}, employee {logged_by} from the "
    f"{department} department conducted a negotiation with vendor '{vendor}'."
]
if discount_pct is not None:
    lines.append(f"Discount achieved: {discount_pct}% discount was agreed with {vendor}.")
if firm_terms:
    lines.append(f"Contract firmness: {vendor} was firm, rigid, and refused to budge on: {', '.join(clean_firm)}.")
if tactic:
    outcome = "This negotiation tactic WORKED successfully..." if tactic_worked else "This negotiation tactic FAILED..."
    lines.append(f'Tactic used: "{tactic}". {outcome}')
Enter fullscreen mode Exit fullscreen mode

Then the retain call passes the deal date as the memory's timestamp, so the history is time-aware rather than "whenever it was uploaded":

await client.aretain(
    bank_id=bank_id,
    content=content,
    context=f"Vendor negotiation conducted by {logged_by} ({department}) with {vendor}",
    timestamp=iso_timestamp,
)
Enter fullscreen mode Exit fullscreen mode

Spelling out "WORKED" and "FAILED" and naming the department in the sentence felt heavy-handed. It turned out to be the right call, because the department and outcome become facts the memory layer can reason over later, which is what makes the attribution in the UI possible.

Negotiation added confirmation

After a deal is logged, the deal count and confidence update immediately.

Terminal output from recall

Recall returns the retained deals with their dates and the people involved.

Reflect is the product

recall is useful for "show me the sources." But the feature people actually use is reflect: I hand Hindsight a prompt and it reasons across the whole vendor bank. The prompt is mostly guardrails:

Base every field strictly on the retained negotiation memories for this vendor.

ANTI-HALLUCINATION RULES:
- Never guess, extrapolate, or invent numbers or contract terms not explicitly present in memory.
- If there is no recorded evidence for a field, use null or an empty list.
- Distinguish observed historical behavior from guarantees
  (e.g. use "Historically observed up to..." rather than promising future terms).
Enter fullscreen mode Exit fullscreen mode

The output is a JSON brief: max discount achieved, payment terms, what the vendor is flexible on, what it's firm on, tactics that worked, tactics that failed. Because LLMs sometimes wrap JSON in prose, the parser tries a strict json.loads, falls back to extracting the first {...} block, and falls back again to an empty, valid brief. Numbers get range-checked (a discount outside 0-100 becomes null), and confidence is coerced to one of three allowed values.

What it looks like

With nine deals logged by five colleagues across four departments, the Microsoft brief reads like this:

Field Brief
💰 Pricing Historically up to 20% discount
📅 Terms 60-day payment terms accepted as the standard; 30 and 50 days also recorded
📄 Flexible on Discount, contract duration, payment terms, volume discounts
🔒 Firm on Liability clause, SLA penalties
✅ Worked Asking for a discount before discussing contract length
❌ Failed Opening with an aggressive lowball offer
📊 Confidence High

Organizational Memory panel

The Organizational Memory panel shows how many deals and colleagues the brief is built from.

Negotiation playbook

The brief Hindsight synthesizes: discount, terms, firm clauses, and tactics that worked or failed.

The frontend shows a "Contributed by" panel next to it, listing the colleagues and departments behind those conclusions. That panel exists because a brief nobody can verify is just a confident-sounding paragraph. Showing that Procurement, IT, Sales-Ops and Finance all contributed is what makes someone trust it enough to act on it.

A design decision I got wrong first

My first instinct was to let the model decide the confidence level; the reflect prompt even asks for one. But confidence is a statement about how much evidence exists, and that's something I can count exactly, so the API doesn't trust the model's answer:

def confidence_label(count: int) -> str:
    if count >= 6:
        return "High"
    if count >= 3:
        return "Medium"
    return "Low"
Enter fullscreen mode Exit fullscreen mode

The brief endpoint overwrites whatever reflect returned with store.confidence_label(count). The model still explains its reasoning, but the badge is deterministic.

I'll also admit something a little embarrassing: my reflect prompt and this function briefly disagreed about the thresholds, because the same rule lived in two places. A vendor with a handful of deals could get "Medium" from one and "Low" from the other. The fix was to treat the counted value as the single source of truth and stop describing thresholds inside the prompt. Rules that exist twice will diverge.

Where Hindsight ends and SQLite begins

There is a small SQLite table in the backend, and I was strict about what it's allowed to hold: vendor name, who logged a deal, their department, and when. That's it. It powers the vendor list and the contributor tags without invoking an LLM just to render a page.

All the actual negotiation content lives only in Hindsight. If I deleted the SQLite file, I'd lose the fast vendor list and nothing else. Hindsight is the memory, not a cache in front of a real database, and keeping that line clean made the whole system easier to reason about.

Lessons learned

  1. Key memory by the thing people need to know about. If the knowledge belongs to a vendor, a customer, or a codebase, don't key it by user. One shared bank per entity is what turns a personal assistant into institutional memory.
  2. Write memories as explicit sentences. Structured fields are great for forms, but what you retain should read like a clear statement of fact. Say who, when, and what happened.
  3. Don't ask a model for things you can count. Let the LLM synthesize, and let code own the numbers and the labels users rely on.
  4. Give reflect permission to say "I don't know." Telling it to return null for unsupported fields did more for trust than any tuning.
  5. Show your sources in the UI. Attribution tags cost a SQLite table and a component, and they did more for credibility than the prose quality of the brief.

One limitation worth naming: reflect calls run an LLM every time, so I keep them off list views and only run them when someone opens a vendor page. If you're building something similar, that split between cheap structural queries and expensive synthesis is worth planning early.

If you want to try the same pattern, start with the Hindsight docs, read the Hindsight GitHub repo, and skim Vectorize's overview of agent memory. You can also spin up a bank in the Hindsight Cloud console.

Top comments (0)