I ran the same negotiation agent twice on the same deal. Same model, same prompt, same tool loop. One brief said, in effect, "I have no idea." The other told me which tactic to lead with, which one to avoid, and who at the vendor actually signs off on a big discount.
The only difference between them was memory. This post is about how I built that memory with Hindsight, why I don't trust an agent's advice unless it can show where it came from, and the one-sentence prompt rule that made my memory agent learn the wrong lesson for seven rounds in a row.
What Precedent does
Precedent is a buyer-side negotiation copilot for procurement teams. Before a vendor renewal it writes a brief: ranked tactics, an opening ask, who really decides on discounts, and what to watch out for. Each claim cites the memory behind it.
The name is borrowed from law. A lawyer doesn't say "trust me", they cite a past case. I wanted the same rule for the agent: no advice without a past case to cite.
The stack is a FastAPI backend, a React dashboard, a tool-calling agent on gpt-4.1, and a local Hindsight server. Hindsight does its own background work (fact extraction, consolidation, playbook rewrites) on the cheaper gpt-4.1-mini. I picked Hindsight's agent memory over a plain vector store because I didn't want to build extraction, belief consolidation and tag-scoped recall myself. If you want background on that distinction, Vectorize's explainer on what agent memory is is a good start.
Two briefs for the same deal
The deal: Kestrel Alloys, a raw-materials supplier, with a multi-year contract for 4,500 tonnes a year coming up for renewal.
Memory OFF. The agent has no history, so it says so. Every tactic is "an experiment", confidence sits around 50%, and there is nothing to check. It is honest, but it is guessing.
Memory ON. The agent opens with a multi-year term, because that tactic won both times we tried it with Kestrel: 12% in August 2025 and 14% in February 2026. It warns against quarter-end pressure, which lost both times (2% and 3%). And it tells me that while the Sales Manager handles day-to-day concessions, larger discounts go to the Chief Commercial Officer.
None of that is in the model's weights. It came from five past negotiations stored in Hindsight. Here is what Hindsight itself has distilled about this vendor, before the agent reads anything:
That playbook is a Hindsight mental model. I created it once per vendor with a source query, and Hindsight rewrites it after each consolidation. I never ask it to.
Memory ON vs OFF is one line
For a fair comparison, the two arms share everything except which tools exist:
registry = {**(MEMORY_TOOLS if use_memory else {}), **BASE_TOOLS}
With memory ON, the agent can call tactic_scoreboard, recall_memory, get_playbook and, at most once per brief, reflect_on_vendor. With memory OFF, it only has market benchmarks and submit_brief. Same model, same system prompt, same number of steps.
Making the ON brief trustworthy
A brief that sounds confident is easy. A brief I'd act on needed three things.
1. Outcomes are counted in code, not by the LLM
My first version only gave the agent prose recall, and it explored badly. From a pile of sentences, it couldn't reliably tell how often a tactic had been tried or how each attempt ended.
The fix was to attach structured metadata when retaining each negotiation:
client().retain(bank_id=bank_id, content=episode_text(ep, vendor),
timestamp=ep["date"], context="Buyer-side negotiation record",
tags=[vtag(vendor["id"]), f"category:{vendor['category']}"],
document_id=f"ep-{vendor['id']}-{ep['date']}-{ep['tactic_used']}",
metadata={"tactic": ep["tactic_used"], "outcome": ep["outcome"],
"final_discount": f"{ep['final_discount']:.3f}",
"ask_discount": f"{ep['ask_discount']:.3f}", "date": ep["date"]})
Hindsight returns that metadata on every fact it recalls, and all facts from one negotiation share a document_id. So a tactic_scoreboard tool can de-duplicate and count exactly:
episodes: dict[str, dict] = {}
for r in res.results:
md = r.metadata or {}
if r.document_id and md.get("tactic") and r.document_id not in episodes:
episodes[r.document_id] = md
The agent sees "multi-year: 2 tries, won, won, best 14% → PROVEN" instead of trying to count from prose. Recall is scoped with tags_match="any_strict", so a brief for Kestrel can never quote another vendor's concessions.
2. Every claim cites a memory you can click
Each item the agent recalls is registered with an id like [m3]. The agent must cite those ids in submit_brief, and the dashboard turns each one into a link to the memory and its date. If a claim has no citation, I treat it as the model's prior, not as memory.
To make the memory itself inspectable, I built an explorer on Hindsight's recall trace. For every result it shows which retrieval channel found it (semantic, keyword, graph or time), where the reranker put it, and the people and companies involved. None of this calls an LLM; the query below took 73 ms.
Hindsight also keeps every earlier version of a playbook and the memories each was built from. When a brief surprised me, I could see when the playbook changed and why.
3. A policy: never under-ask
This is the part I got wrong.
The rule that taught my memory the wrong lesson
My original prompt told the agent to open near the best discount the vendor had ever given. It sounds sensible. It was a death spiral.
Early asks were small, around 8%. A low ask gets a low result. The next ask was anchored to that low result, so it went lower. Worse, it corrupted learning. In the evaluation, the tactic that actually works on Northwind (a competing quote) was tried four times and "lost" every time, because the ask was too small for it to matter. Memory faithfully recorded four failures of the right tactic.
For seven rounds the memory agent was no better than the one without memory. Recall was returning the right notes the whole time. The bug was my sentence.
The fix was a stated policy, and the memory layer did not change at all:
"Opening ask: if a tactic is PROVEN, open about 10%% above the best discount it has achieved; "
"otherwise open at 18%% off list. Never under-ask: a low ask makes a good tactic look like it "
"failed and poisons what we learn. "
The 18% and +10% are my procurement heuristics, not something the agent learned. Memory tells the agent whether a tactic is proven. The policy tells it what to do with that.
Does it actually learn?
To test that, I wrote a simulator with three vendors. Each has hidden rules the agent never sees: one tactic that works, one that backfires, and a maximum discount. Both agents run ten negotiations per vendor, starting from an empty memory bank. After each round, the memory agent's outcome is retained into Hindsight. The score is the discount achieved as a share of that vendor's maximum.
| Negotiation | Memory ON | Memory OFF |
|---|---|---|
| 1 | 44.0 | 40.5 |
| 2 | 68.6 | 41.9 |
| 4 | 78.2 | 36.3 |
| 7 | 68.4 | 30.0 |
| 10 | 76.2 | 44.5 |
Negotiation 1 is a cold start for both, so they're close. Over the last five, memory averages 75.6% of the maximum discount against 39.9% without it. Counting a "win" as at least 85% of the maximum, the memory agent wins 2 of 3 vendors from negotiation 4 onward. The agent without memory never wins one.
What hurt along the way
- A local 8B model was too slow for extraction: about four minutes per retain on my 8GB GPU.
- Free-tier rate limits couldn't hold the load, so I split the roles: a small model for Hindsight's background work, a larger one for the agent.
- The sync Hindsight client binds to its creating thread. One client per thread fixed intermittent timeouts.
- A laptop sleep silently killed a long evaluation. It now retries and checkpoints after every negotiation.
Lessons
- Test your prompt as hard as your memory. My biggest bug looked like a memory failure and wasn't one.
- A good action with bad parameters looks like a bad action. If you score outcomes, make sure the inputs that produced them aren't themselves shrinking because of earlier bad outcomes.
- Give the agent structured views of its own history. Store outcomes as metadata at retain time and count them in code.
- Make memory auditable. Citations in the answer, a recall trace in the UI and playbook history turned "trust me" into "check me".
Limits
The negotiation history is synthetic: 12 fictional vendors and 61 negotiations, because no public dataset contains negotiation notes, and inventing them about real companies would be fabrication. The market benchmarks are real: 19,133 awards from Estonia's public procurement register. The simulator and the opening-ask policy are mine, so the learning curve shows that the memory loop works, not real-world savings. It is one run on three vendors with no confidence intervals, and one vendor was never solved.





Top comments (0)