Siva TejaSupplier negotiation memory agent dashboard 1. Hook: memory retrieval can be relevant and...
Supplier negotiation memory agent dashboard
A supplier negotiation agent can retrieve a memory that looks relevant to the question and still give the wrong advice. A previous price for a microcontroller might be useful when comparing microcontroller suppliers, but it is misleading as evidence of how a different supplier negotiated an industrial motor deal.
That distinction shaped the engineering problem in this project. Retrieval must find useful history, but the application must decide which parts of that history belong in a supplier-specific negotiation and which belong in a product-wide comparison. If I put every plausible result into the reflection prompt, the model has no reliable way to know which supplier’s behavior it is describing.
Hindsight gives me explicit operations for storing, retrieving, and reasoning over agent memory. Its recall combines semantic, keyword, graph, and temporal retrieval strategies, then reranks results. That breadth is useful for finding candidates; it does not replace the application’s need to enforce supplier and product boundaries. (Hindsight on GitHub, Hindsight documentation)
I built a B2B negotiation workflow around a quote: supplier, product, quantity, unit price, delivery fee, and payment terms. The React application collects the quote and calls a FastAPI backend. The backend initializes the Python Hindsight client from configuration and uses a shared bank ID for the negotiation memory.
The core sequence is:
That is the project’s learning loop. It is also a small example of a broader agent-memory problem: long-term memory matters only when the system surfaces the right information into the current reasoning context. (Vectorize’s agent-memory overview)
The backend’s /api/negotiation/analyze endpoint issues two recall queries. One asks for a supplier’s history involving the current product; the other asks for procurement history about that product across suppliers:
supplier_recall = hindsight.recall(
bank_id=BANK_ID,
query=supplier_query
)
product_recall = hindsight.recall(
bank_id=BANK_ID,
query=product_query
)
Negotiation workspace showing Hindsight memory recall
The separation is deliberate. Supplier history can reveal that a particular vendor waived freight after a volume commitment. Product history can provide market context even when the current supplier is new. They answer different questions, so the code filters and returns them in separate supplier_memories and product_memories arrays.
The /api/negotiation/reflect endpoint performs its own recall, applies the same filters, and builds distinct supplier-memory and product-memory blocks for Hindsight’s reflect call. That way, the reflection receives labeled context instead of one undifferentiated collection of memories. Hindsight’s own documentation describes recall and reflect as separate operations; the project uses that distinction directly rather than hiding both inside one opaque frontend request. (Hindsight API documentation)
Supplier history and product history have different scopes.
Supplier history answers: “What has this supplier done in previous negotiations?” A record explicitly identifying TechCore should not become Crompton’s negotiation history just because both records mention a quote or delivery.
Product history answers: “What outcomes have been recorded for this product?” It can legitimately include multiple suppliers. A new supplier may have no history of its own while the product still has useful price or concession context from another vendor.
The backend preserves that distinction while filtering:
supplier_memories = [
m for m in raw_supplier_texts
if is_memory_relevant_to_supplier(m, quote.supplier, quote.product)
]
product_memories = [
m for m in raw_product_texts
if is_memory_relevant_to_product(m, quote.product)
]
The combined memories field remains for frontend compatibility, but it is built from these filtered buckets. Product-wide results do not become supplier-specific records in the backend response.
Semantic relevance is not entity identity. A memory can mention “industrial” without describing an Industrial Motor negotiation; a supplier’s name can occur in the body of a memory about another supplier. Generic terms such as “supplier,” “product,” “quote,” and “units” are especially weak evidence because they occur in many negotiation records.
I changed the backend matching to give explicit labels priority. When a memory contains a Supplier: field, the supplier matcher checks that field against the requested supplier and rejects a conflict. Unstructured text is matched against its opening title or label, so a later incidental mention does not assign the memory to that supplier.
Product matching is similarly conservative. An explicit Product: value must match the requested product. Without that field, the matcher accepts a full product phrase or sufficiently strong entity terms; generic words alone do not establish a product match. This is application-level filtering after Hindsight recall, not a claim that Hindsight’s retrieval is defective.
The same helpers are used by both analyze and reflect. In addition, /api/negotiation/analyze now returns recalled_memory as a newline-joined string of the filtered combined memories. It no longer returns the raw supplier recall object through that field. The reflect prompt is also constructed from the filtered supplier and product blocks, so raw recall candidates do not flow directly into strategy generation.
AI negotiation strategy generated from historical memory
When a user records a negotiation outcome, the frontend sends supplier, product, summary, outcome, concession, final price, final delivery fee, and lessons to /api/negotiation/outcome. The backend formats those values as labeled text and passes that text to Hindsight’s retain operation:
experience = "\n".join(lines)
hindsight.retain(
bank_id=BANK_ID,
content=experience
)
Negotiation outcome being saved to Hindsight memory
The labels matter to the later filter. Supplier: and Product: give future recall results explicit entity fields to check instead of relying only on loose words in a narrative.
On a later negotiation, analyze and reflect issue new queries against the same bank. If Hindsight returns the retained outcome, the backend evaluates it for the supplier-specific or product-wide bucket before returning or using it.
The frontend’s API service distinguishes live backend results from fallback data. If the backend request fails, it returns the supplied demo fallback as non-live. That keeps the interface usable, but it also means a displayed strategy is not necessarily evidence of a successful Hindsight recall or retain.
The repository includes a canonical demo preset for TechCore Semiconductors and Microcontroller (MCU). It shows a previous quote of ₹192 per unit, a successful price of ₹178 per unit, free delivery, and an order size of 8,000 units. For the demo’s current 10,000-unit quote, its suggested counter is ₹178 per unit with free delivery. Those values are fixture data in demoData.js; they are not proof that a live Hindsight bank contains that negotiation.
In the live workflow, a user can record an outcome with the same supplier and product. The backend retains the labeled outcome. A later analyze request then issues both supplier/product-scoped recall queries. That retained record can qualify as TechCore supplier history and Microcontroller product history. The application can use exact supplier-product matches when extracting a numeric benchmark, while reflect can also reason over the filtered context.
If the next negotiation is with a new supplier, the supplier-specific bucket should not inherit TechCore’s history. The product-wide bucket can still contain matching Microcontroller records from other suppliers, because that is a different scope. In the current frontend, exact supplier-product matching gates numeric benchmark extraction; cross-supplier product context can reach reflection, but does not automatically replace the numeric counter-offer target.
My main lesson was that “relevant” is not a sufficient contract for memory. Recall supplies candidates; application code must enforce the entity scope required by the task. Generic tokens and partial product matches made that boundary too permissive, so I tightened the post-recall checks rather than assuming semantic retrieval alone could encode procurement identity.
There are limits. The application uses one configured bank and enforces supplier separation through filtering, not separate supplier banks or access controls. The filter is therefore an application relevance boundary, not a privacy or authorization boundary. Its stopword and entity-matching rules also need continued evaluation as new supplier names, product names, and retained text formats appear.
The UI adds another distinction: it uses exact supplier-product matches for benchmark extraction, while reflect can receive product-wide history from other suppliers. That choice is conservative for numeric price comparisons, but it means the product context may inform narrative strategy without changing the deterministic numeric target. The project is an MVP, not a production-proven negotiation system; its demo presets and offline fallbacks should be treated as such.
I built the loop so a quote can lead to recall, reflection, a counter-offer, an outcome, and a later recall of that outcome. The essential engineering decision was to keep supplier-specific behavior separate from product-wide context all the way into reflection. Hindsight provides the memory operations; the application supplies the procurement-specific entity checks that make their results usable.