A sales briefing can be wrong while every sentence in it is individually true: the CFO’s objection from one account gets attached to another account, and a rep walks into the call with someone else’s pricing history. In this project, one field—deal_id—is the difference between useful recall and a confident account mix-up.
I built Deal Intelligence Agent as a FastAPI service with three useful API paths. POST /deals/{deal_id}/log retains a call, email, or meeting note in Hindsight Cloud and appends a local copy. GET /deals/{deal_id}/brief recalls one deal’s history and asks Groq’s openai/gpt-oss-120b to produce a structured briefing. GET /patterns performs a global recall and asks Groq to find one recurring objection-resolution pattern across deals. The web UI is vanilla HTML, CSS, and JavaScript; deals.json supplies deal names and metadata for local inspection.
The division of labor matters. Hindsight Cloud is the persistent memory engine, using retain, recall, and its TEMPR retrieval strategy. It is not the final answer generator. The application decides what scope to search, passes the retrieved text to Groq, and returns a response. That makes the application code—not an invisible prompt convention—the place where the most important boundary is expressed.
A single shared memory bank is convenient for cross-deal analysis, but dangerous for a deal briefing. The same bank can contain every account’s notes, so every stored item is tagged with its deal identifier. On retain, I put the identifier in three places: in the human-readable content prefix, in the Hindsight tags, and in metadata. Only the tags are used by this implementation to filter recall; the other two carry useful context and traceability.
The request path is intentionally boring: the browser calls FastAPI, FastAPI chooses the memory scope, Hindsight Cloud retains or recalls, and Groq turns recalled text into a briefing or playbook rule. The local JSON store only supplies deal metadata and an inspection-friendly copy of logs.
flowchart TD
UI["Web UI<br/>(dark mode, vanilla HTML/CSS/JS)"]
API["FastAPI backend<br/>(app/main.py)"]
H["Hindsight Cloud<br/>(retain / recall / TEMPR)"]
B["deal-intel bank"]
G["Groq<br/>(openai/gpt-oss-120b)"]
J["deals.json<br/>(metadata and local log copy)"]
UI -->|log, brief, patterns| API
API -->|tagged retain / scoped recall| H
H --> B
API -->|recalled context| G
API --> J
G -->|briefing or playbook rule| API
API --> UI
This separation also explains why the project can support both precision recall and cross-deal analysis. /deals/{deal_id}/brief follows the tagged path. /patterns intentionally opens the recall scope across the bank, then asks for one evidence-backed pattern instead of a general summary.
For a local run, I create a Python 3.10+ environment, install requirements.txt, set GROQ_API_KEY, HINDSIGHT_API_KEY, HINDSIGHT_BASE_URL, HINDSIGHT_BANK_ID=deal-intel, and GROQ_MODEL, then seed the synthetic records before starting Uvicorn. The seed step matters: a clean Hindsight bank should produce the cold-start response, while the seeded bank makes the objection-resolution arc inspectable.
python scripts/generate_synthetic_deals.py
uvicorn app.main:app --reload --port 8000
payload = {
"items": [
{
"content": f"[Deal {deal_id}] {text.strip()}",
"tags": [deal_id],
"metadata": {
"deal_id": deal_id,
"source": "deal-intel-agent"
}
}
]
}
The recall helper has two modes. For an individual briefing, it requires a deal ID and sends it as the tag filter. For cross-deal pattern detection, it deliberately leaves the filter out. The explicit branch is a useful little piece of policy: a normal query is scoped, and global recall requires the caller to ask for it by name.
if scope == "deal":
if not deal_id:
raise ValueError("deal_id must be provided when scope is 'deal'")
payload["tags"] = [deal_id]
payload["tags_match"] = "any"
That code is simple, but the simplicity should not be mistaken for a security boundary. It is an application-level retrieval filter over a shared bank. A production deployment with multiple customers would also need authorization around deal IDs, controlled bank access, and tests that prove one customer cannot request another customer’s identifier. The filter reduces accidental cross-deal contamination in the intended flow; it does not authenticate the caller. It also assumes deal identifiers are canonical and consistently attached when records enter the system. Missing or mistyped tags are an ingestion problem that retrieval cannot infer away.
The briefing endpoint keeps the same pattern visible from the API layer. It asks Hindsight for relevant deal history, extracts text, then hands that context to the language model. The prompt is not expected to repair a bad scope decision downstream.
memories = await recall_deal(
deal_id,
query="full deal history, objections, stakeholders, competitors",
scope="deal"
)
context_texts = [m.get("text", "") for m in memories if m.get("text")]
briefing_text = generate_briefing(
context_texts,
query=f"Briefing for {company_name or deal_id}"
)
There is a deliberate asymmetry in the other direction. Pattern detection needs a wide view, so /patterns calls recall with scope="all" and then asks Groq to identify exactly one concrete repeating pattern. The model is given an instruction to cite examples and state when the evidence is too thin. The code also has a basic minimum of two recalled items, although two items alone do not establish a robust statistical correlation. A fluent sentence is a lead for a human to inspect, not a measured causal claim.
This is where Hindsight fits the project: the external memory service provides retain and recall over a bank, while the application decides how to tag and scope account records. The Hindsight documentation describes its memory API and retrieval operations. Vectorize’s agent-memory overview gives useful context for the broader idea of persistent agent memory; this repository uses a narrower slice of that design, centered on deal-tagged recall and synthesis.
Take the seeded ApexLogistics account. Before history exists, generate_briefing does not ask the model to improvise. It returns a deterministic cold-start briefing: no objections, stakeholders, or competitors are recorded, and the next move is a discovery call. That is a modest but important fallback. Empty memory should be represented as empty memory, not filled with generic confidence.
After five interactions, the repository’s sample account has a much more useful sequence: Elena likes the route-optimization product but reports that LegacyFreight is 25% cheaper; a technical evaluation goes well; Marcus, the CFO, objects to the $6,000 monthly price while the quarter’s budget is constrained; the rep offers annual prepayment at 15% off and waives implementation fees; Marcus then approves the budget and signs. A deal-scoped recall can turn that sequence into a playbook entry: identify the champion, involve the economic veto early, treat price and budget timing as the real obstacle, and test annual prepayment when the customer’s accounting treatment rewards it. The outcome is part of the lesson; without the signed agreement, the discount would be just another attempted concession.
The global path turns that single history into a testable playbook hypothesis. With ApexLogistics and CloudScale Systems in the bank, /patterns can surface a pattern such as: when a CFO or procurement team objects to price or cites a cheaper competitor, annual prepaid billing, a 15% discount, and waived onboarding can move a stalled opportunity toward Closed-Won. That is more useful than “discount when the buyer pushes back” because it preserves the objection, the response, and the observed outcome together. It is also still a hypothesis; the endpoint is not a causal inference system.
That before-and-after is grounded in the checked-in synthetic deal record, not a measured user study. The separate Hindsight bank only contains those interactions if the seed script has actually been run against a configured service. And a specific briefing depends on what recall returns: retrieval can omit a relevant note, or rank an old note above a newer one. The application exposes a recalled count, but it does not yet provide a source-by-source audit trail in the response.
deals.json stores account metadata and a local log copy. They are separate writes, so a failure between them can leave them out of sync. The API currently retains remotely before saving locally.
The central lesson is not that an agent can remember everything. It is that memory only helps when the application can state whose history it is retrieving, expose enough provenance to check the answer, and admit when the evidence is too thin. In Deal Intelligence Agent, deal_id is the beginning of that contract—and the remaining production work is making the contract enforceable end to end.