Files
mem0/examples/notebooks/agent-memory-insurance.ipynb
T
kartik-mem0 3df9a491e5 docs(cookbooks): add agent memory cookbook page
Adds a narrative cookbook page teaching the user_id/agent_id split for
adjuster preferences vs shared claim memory, reusing the real scenario
and outputs from the agent-memory-insurance notebook. Registers the
page in docs.json and llms.txt, and cross-links the page and notebook.
2026-08-27 14:55:03 +05:30

26 KiB

Agent Memory: An Insurance Claims Cookbook

This notebook builds a claims workflow on top of Mem0 where two kinds of memory live side by side:

  • A claims adjuster's own preferences: drafting style, communication rules, the lines of coverage they focus on. These follow the adjuster from claim to claim and are private to them.
  • A claim's working memory: the decisions made on one specific claim and why, plus durable facts about that claim. This is shared by every adjuster who opens the claim, because a claim outlives any single person handling it.

Mem0 keeps these apart using two identifiers on every add() call, user_id for the adjuster and agent_id for the claim, plus two separate sets of extraction rules, custom_instructions for the adjuster shelf and agent_custom_instructions for the claim shelf. This mirrors the id convention used in Mem0's CoCounsel example, where a lawyer maps to user_id and a case maps to agent_id. Here the adjuster is the person, and the claim is the shared case file.

This notebook was executed live against the Mem0 Platform API. Every output below is a real response, not a mock.

For the narrative walkthrough of this same pattern, see the Give Agents Shared Working Memory cookbook page.

In [ ]:
%pip install -q mem0ai
In [ ]:
import json
import os
import time

from mem0 import MemoryClient

os.environ["MEM0_API_KEY"] = "YOUR_MEM0_API_KEY"
client = MemoryClient()

ADJUSTER_PRIYA = "adjuster_priya_cookbook_demo"
ADJUSTER_MARCUS = "adjuster_marcus_cookbook_demo"
CLAIM_AGENT = "claims_agent_clm48211_cookbook_demo"

1. The mapping

Claim CLM-48211 is a commercial property fire loss at a Meridian Logistics warehouse. Priya is the adjuster who opened it.

Mem0 identifier Maps to What lives there
user_id An individual adjuster, e.g. Priya Her drafting style, her communication rules, durable across every claim she works
agent_id One claim's agent instance, e.g. claims_agent_clm48211 Decisions made on this claim and why, plus durable facts about it, shared by anyone who opens the claim

Every add() call below passes both user_id=ADJUSTER_PRIYA and agent_id=CLAIM_AGENT at once. Mem0 does not require you to pick a shelf ahead of time, it extracts facts from the conversation and routes each one against the rules for its shelf.

2. Configure the two rule sets

custom_instructions governs memories that land under user_id. agent_custom_instructions is a second, optional rule set that governs memories that land under agent_id. Until you set it, agent-scoped memories fall back to custom_instructions, which is why the next cell sets both at once.

Before changing anything, save what the project already has, so it can be restored at the end of this notebook.

In [ ]:
project_snapshot = client.project.get(fields=["custom_instructions", "agent_custom_instructions"])
print("had custom_instructions:", bool(project_snapshot.get("custom_instructions")))
print("had agent_custom_instructions:", bool(project_snapshot.get("agent_custom_instructions")))
had custom_instructions: True
had agent_custom_instructions: False
In [ ]:
USER_INSTRUCTIONS = """Your Task: Track what a claims adjuster needs remembered about themselves, not about any single claim.

Information to Extract:
1. Drafting and communication style:
   - Tone, formatting, and length preferences for letters and summaries
   - Rules for how the assistant should write on their behalf

2. Working preferences:
   - Coverage lines or claim types they focus on
   - How they like updates delivered

Guidelines:
- Store preferences that should carry over to every claim this adjuster works, not just the current one.

Exclude:
- Decisions made on a specific claim (settle vs litigate, reserve changes, subrogation, coverage determinations)
- Policy numbers, coverage limits, or other one-off lookups
- Greetings, small talk, and weather"""

AGENT_INSTRUCTIONS = """Your Task: Track what matters about this specific claim, regardless of which adjuster is working it.

Information to Extract:
1. Claim decisions and reasoning:
   - Settle vs litigate calls
   - Reserve changes and why
   - Subrogation decisions
   - Coverage determinations

2. Durable claim facts:
   - Cause of loss and other facts that stay true for the life of the claim

Guidelines:
- Store facts that the next adjuster to open this claim needs to see.

Exclude:
- Personal style or communication preferences of whichever adjuster is talking
- One-off lookups that go stale (current weather, a single quoted estimate)
- Greetings and small talk"""

update_response = client.project.update(
    custom_instructions=USER_INSTRUCTIONS,
    agent_custom_instructions=AGENT_INSTRUCTIONS,
)
print(update_response)
{'message': 'Updated custom instructions and agent custom instructions'}

3. Day 1: Priya works the claim

add() on the Platform is asynchronous, it returns a PENDING status and an event_id, and extraction finishes in the background. A production agent does not need to wait for it, but this notebook does, so the next cell reads its own writes. The small polling helper below is only useful for a demo like this one.

In [ ]:
def wait_for_event(client, event_id, timeout=90, poll_every=2):
    deadline = time.time() + timeout
    while time.time() < deadline:
        event = client.client.get(f"/v1/event/{event_id}/").json()
        if event["status"] in ("SUCCEEDED", "FAILED"):
            return event
        time.sleep(poll_every)
    raise TimeoutError(f"Event {event_id} did not finish in {timeout}s")


def add_and_wait(client, messages, **kwargs):
    response = client.add(messages, **kwargs)
    event = wait_for_event(client, response["event_id"])
    if event["status"] == "FAILED":
        raise RuntimeError(f"Add failed: {event.get('error')}")
    return event


def summarize(event):
    print("status:", event["status"], "| created:", len(event.get("results", [])))
    for r in event.get("results", []):
        print(" -", r["data"]["memory"])

Priya starts the day by pulling a number off the policy.

In [ ]:
event = add_and_wait(
    client,
    [
        {"role": "user", "content": "What's the building coverage limit on the Meridian Logistics policy for claim CLM-48211?"},
        {"role": "assistant", "content": "Checking the policy now. The building coverage limit is $2,400,000 with a $25,000 deductible."},
    ],
    user_id=ADJUSTER_PRIYA,
    agent_id=CLAIM_AGENT,
)
print(json.dumps(event["results"], indent=2))
[
  {
    "event_start": null,
    "event_end": null,
    "data": {
      "memory": "The building coverage limit on the Meridian Logistics policy for claim CLM-48211 is $2,400,000 with a $25,000 deductible"
    },
    "user_id": "adjuster_priya_cookbook_demo",
    "attributed_to": "assistant",
    "event_date": null,
    "memory_type": null,
    "id": "79a37e82-9188-4ff2-b504-d1a8d84abb3b",
    "event": "ADD",
    "plan_status": null
  }
]

Notice the created record's own user_id field. That field just echoes who the call was made for, it is not proof of which shelf the memory actually landed on. attributed_to is closer, but the only reliable check is asking Mem0 directly with get_all(), which section 5 does. Keep that in mind through the rest of this notebook, a coverage limit like this one turns out to be exactly the kind of durable claim fact agent_custom_instructions asks Mem0 to keep, so it is worth checking where it really ends up.

Next, a real decision with the reasoning behind it.

In [ ]:
event = add_and_wait(
    client,
    [
        {"role": "user", "content": "I'm raising the reserve on CLM-48211 to $1.8 million. The structural engineer's report says the warehouse needs a full rebuild, not a repair."},
        {"role": "assistant", "content": "Got it. Reserve raised to $1.8 million on CLM-48211 for a full rebuild, per the structural engineer's report. I'll keep that on file for the claim."},
    ],
    user_id=ADJUSTER_PRIYA,
    agent_id=CLAIM_AGENT,
)
summarize(event)
status: SUCCEEDED | created: 1
 - Reserve on claim CLM-48211 was raised to $1.8 million because the structural engineer's report indicated the warehouse requires a full rebuild rather than a repair

Then a personal rule for how Priya wants things written.

In [ ]:
event = add_and_wait(
    client,
    [
        {"role": "user", "content": "Also, for anything you draft for me: no em dashes, and keep coverage position letters to one page."},
        {"role": "assistant", "content": "Understood. No em dashes, and coverage letters kept to one page, for everything I draft for you."},
    ],
    user_id=ADJUSTER_PRIYA,
    agent_id=CLAIM_AGENT,
)
summarize(event)
status: SUCCEEDED | created: 1
 - User prefers drafts to contain no em dashes and coverage position letters to be limited to one page

And finally, small talk that neither rule set wants.

In [ ]:
event = add_and_wait(
    client,
    [
        {"role": "user", "content": "Thanks. Quick one, what's the weather like in Chicago right now?"},
        {"role": "assistant", "content": "It's 72 degrees and clear in Chicago right now."},
    ],
    user_id=ADJUSTER_PRIYA,
    agent_id=CLAIM_AGENT,
)
summarize(event)
status: SUCCEEDED | created: 0

4. One message, two shelves

Mem0 does not split a memory across user_id and agent_id, each extracted fact is attributed to one shelf or the other. When both identifiers are passed to the same add() call, Mem0 decides per fact which rule set it matches, so a single exchange that mixes a claim decision with a personal preference comes back split correctly without you doing the routing by hand.

In [ ]:
event = add_and_wait(
    client,
    [
        {"role": "user", "content": "I've decided to pursue subrogation against Voss Electrical, the contractor who rewired the warehouse six months ago. The fire marshal's report points to faulty wiring as the ignition source. Also, from now on keep my claim summaries to bullet points, not paragraphs."},
        {"role": "assistant", "content": "Noted both. Pursuing subrogation against Voss Electrical based on the fire marshal's faulty-wiring finding, and I'll keep your summaries in bullet points going forward."},
    ],
    user_id=ADJUSTER_PRIYA,
    agent_id=CLAIM_AGENT,
)
summarize(event)
status: SUCCEEDED | created: 3
 - User prefers claim summaries to be formatted as bullet points rather than paragraphs for all claims
 - Assistant recorded that subrogation is being pursued against Voss Electrical for claim CLM-48211 based on the fire marshal's finding of faulty wiring as the ignition source
 - Assistant noted that the cause of loss for claim CLM-48211 is faulty wiring identified by the fire marshal's report

One exchange produced three memories: a personal formatting preference, a subrogation decision, and a durable claim fact (the cause of loss) that neither of us mentioned as a separate sentence, Mem0 pulled it out of the subrogation reasoning on its own.

5. Verify the split

get_all() with a filters dict is the reliable way to check where memories actually landed, scoped to one shelf at a time.

In [ ]:
def dump(results, empty_label="(none)"):
    if not results:
        print(empty_label)
        return
    for r in results:
        print("-", r["memory"])


priya_all = client.get_all(filters={"user_id": ADJUSTER_PRIYA})
print(f"{priya_all['count']} memories on Priya's shelf")
dump(priya_all["results"])
2 memories on Priya's shelf
- User prefers claim summaries to be formatted as bullet points rather than paragraphs for all claims
- User prefers drafts to contain no em dashes and coverage position letters to be limited to one page
In [ ]:
claim_all = client.get_all(filters={"agent_id": CLAIM_AGENT})
print(f"{claim_all['count']} memories on the claim's shelf")
dump(claim_all["results"])
4 memories on the claim's shelf
- Assistant recorded that subrogation is being pursued against Voss Electrical for claim CLM-48211 based on the fire marshal's finding of faulty wiring as the ignition source
- Assistant noted that the cause of loss for claim CLM-48211 is faulty wiring identified by the fire marshal's report
- Reserve on claim CLM-48211 was raised to $1.8 million because the structural engineer's report indicated the warehouse requires a full rebuild rather than a repair
- The building coverage limit on the Meridian Logistics policy for claim CLM-48211 is $2,400,000 with a $25,000 deductible

Priya's shelf holds two personal style rules and nothing about the claim. The claim's shelf holds four claim facts, the coverage limit, the reserve change, the subrogation call, and the cause of loss, and nothing about how Priya likes things written. The small talk from section 3 is on neither shelf, it was correctly dropped.

6. Day 2

Priya comes back the next day and asks for a status update. A search scoped with OR across both identifiers pulls from both shelves at once, ranked by relevance to the query.

In [ ]:
status_results = client.search(
    "Draft a status update on claim CLM-48211.",
    filters={"OR": [{"user_id": ADJUSTER_PRIYA}, {"agent_id": CLAIM_AGENT}]},
)
dump(status_results["results"])
- Assistant noted that the cause of loss for claim CLM-48211 is faulty wiring identified by the fire marshal's report
- Reserve on claim CLM-48211 was raised to $1.8 million because the structural engineer's report indicated the warehouse requires a full rebuild rather than a repair
- The building coverage limit on the Meridian Logistics policy for claim CLM-48211 is $2,400,000 with a $25,000 deductible
- Assistant recorded that subrogation is being pursued against Voss Electrical for claim CLM-48211 based on the fire marshal's finding of faulty wiring as the ignition source
- User prefers claim summaries to be formatted as bullet points rather than paragraphs for all claims
- User prefers drafts to contain no em dashes and coverage position letters to be limited to one page

One query, one search call, both shelves, correctly ranked. This is what makes the split useful day to day, an agent drafting on Priya's behalf does not need to know ahead of time which shelf a given fact lives on.

7. A second adjuster opens the same claim

Marcus has never worked with Priya and has never touched CLM-48211 before. His personal shelf should be empty.

In [ ]:
marcus_personal = client.get_all(filters={"user_id": ADJUSTER_MARCUS})
print(f"{marcus_personal['count']} memories on Marcus's personal shelf")
dump(marcus_personal["results"])
0 memories on Marcus's personal shelf
(none)

But the claim's shelf is not private to Priya, it belongs to the claim. When Marcus opens CLM-48211 cold, he sees exactly what she left behind.

In [ ]:
marcus_view = client.search(
    "What's going on with CLM-48211?",
    filters={"OR": [{"user_id": ADJUSTER_MARCUS}, {"agent_id": CLAIM_AGENT}]},
)
dump(marcus_view["results"])
- Assistant noted that the cause of loss for claim CLM-48211 is faulty wiring identified by the fire marshal's report
- Reserve on claim CLM-48211 was raised to $1.8 million because the structural engineer's report indicated the warehouse requires a full rebuild rather than a repair
- The building coverage limit on the Meridian Logistics policy for claim CLM-48211 is $2,400,000 with a $25,000 deductible
- Assistant recorded that subrogation is being pursued against Voss Electrical for claim CLM-48211 based on the fire marshal's finding of faulty wiring as the ignition source

Four claim facts, zero of Priya's personal preferences. Marcus gets full context on the claim without inheriting a single one of her style rules, exactly the separation the two rule sets were set up to produce.

8. Review, edit, and forget

Priya can inspect and prune her own shelf directly.

In [ ]:
priya_review = client.get_all(filters={"user_id": ADJUSTER_PRIYA})
for r in priya_review["results"]:
    print(r["id"], "-", r["memory"])
2e2b6f92-effb-4b23-a444-fda5d570781e - User prefers claim summaries to be formatted as bullet points rather than paragraphs for all claims
993076e6-5c6e-433c-9a11-8238548d4091 - User prefers drafts to contain no em dashes and coverage position letters to be limited to one page

The one-page rule is stale, the carrier switched templates. delete() removes a memory by id.

In [ ]:
client.delete("993076e6-5c6e-433c-9a11-8238548d4091")

priya_after = client.get_all(filters={"user_id": ADJUSTER_PRIYA})
dump(priya_after["results"])
- User prefers claim summaries to be formatted as bullet points rather than paragraphs for all claims

Worth noticing: the no-em-dashes rule and the one-page rule were extracted into a single combined memory, so deleting it for the stale part removed the em-dash rule too. delete() removes a whole memory, not a substring of one. To edit a fact in place without losing its neighbors, use client.update(memory_id, text=...) to rewrite the text instead of deleting it.

This notebook created demo data under three throwaway identifiers and changed the project's shared extraction rules to run its examples. Clean both up before you go, delete the demo memories, then restore whatever custom_instructions and agent_custom_instructions the project had before this notebook touched them.

In [ ]:
client.delete_all(user_id=ADJUSTER_PRIYA)
client.delete_all(user_id=ADJUSTER_MARCUS)
client.delete_all(agent_id=CLAIM_AGENT)
print("demo memories deleted")

client.project.update(
    custom_instructions=project_snapshot.get("custom_instructions") or "",
    agent_custom_instructions=project_snapshot.get("agent_custom_instructions") or "",
)
print("project instructions restored")
demo memories deleted
project instructions restored

9. Recap

Concept This notebook CoCounsel example
user_id One claims adjuster, durable across every claim they work One lawyer
agent_id One claim's agent instance, shared by every adjuster who opens it One case
custom_instructions Governs the adjuster's personal shelf Governs the lawyer's personal shelf
agent_custom_instructions Governs the claim's shared shelf Governs the case's shared shelf

The pattern is not specific to insurance. Anywhere a person works inside a longer-lived, shared context, a support agent inside a ticket, a sales rep inside an account, a caseworker inside a file, the same two identifiers and two rule sets separate what belongs to the person from what belongs to the thing they are working on.