Support agents that remember the customer, not just the ticket
Store what you know about each customer once, recall it on every ticket, keep personal data out of the prompt, and erase all of it when a customer asks. No services to run while you build it.
npm install actrone-memory · pip install actrone-memory
Every ticket starts from zero
Customers explain themselves again
Their plan, their setup and the last fix all have to be restated, because the agent forgot them when the previous ticket closed.
Personal data rides along in every prompt
Pasting the whole CRM record into context sends email addresses and phone numbers to the model on every turn, whether the question needs them or not.
Deletion requests mean a search
When a customer asks to be forgotten, their details are spread across prompts, caches and logs with no single place to remove them.
Five steps, one scope per customer
customer:1042, and recall can never cross from one customer to another.Import what you already know
injectMemory, in Python inject_memory
Store each fact from your CRM once, with where it came from (
import:crm) and a sensitivity tag:none,low,piiorsensitive.Recall for each ticket
retrieveContext, in Python retrieve_context
Returns the ticket’s recent turns and the customer’s most relevant facts for the question, pruned to the token budget you pass.
Filter before the model
m.sensitivity
Every recalled fact carries its tag, so one filter keeps pii and sensitive facts out of the prompt. The filter is your code: the library returns tags and does not redact.
Keep the conversation
storeTurn, in Python store_turn
Records the question and the reply in the ticket’s session, so the next message on the same ticket sees them.
Erase on request
eraseAgentMemories, in Python erase_agent_memories
Deletes the customer’s long-term memories and the ticket’s recent turns in one call.
The whole integration, in both languages
callModel is your own model call; the library never calls a model here. The test suites of both packages run this code and check that customers stay apart, that the email never reaches the model and that one call erases the customer.import { MemoryManager } from "actrone-memory";
// Long-term memory is scoped by its first argument, so one scope per customer keeps them apart.
const scopeFor = (customerId: string) => `customer:${customerId}`;
// Seed what your CRM already knows, tagged with its source and how sensitive it is.
export async function importAccount(memory: MemoryManager, customerId: string): Promise<void> {
const scope = scopeFor(customerId);
// Arguments: scope, fact, importance, session label, topic tags, source, sensitivity.
await memory.injectMemory(scope, "The customer is on the Business plan and renews in March.",
0.9, "crm", ["plan"], "import:crm", "none");
await memory.injectMemory(scope, "The customer billing email is dana@example.com.",
0.6, "crm", ["contact"], "import:crm", "pii");
}
// Answer a ticket with what you know about this customer, minus personal data.
export async function answerTicket(
memory: MemoryManager,
customerId: string,
ticketId: string,
question: string,
callModel: (question: string, facts: string[]) => Promise<string>,
): Promise<string> {
const scope = scopeFor(customerId);
const context = await memory.retrieveContext(scope, ticketId, question, 2000);
const facts = context.episodicMemories
.filter((m) => m.sensitivity === "none" || m.sensitivity === "low")
.map((m) => m.content);
const reply = await callModel(question, facts);
await memory.storeTurn(scope, ticketId, question, reply);
return reply;
}
// A deletion request: erase the customer's memories and this ticket's turns.
export async function forgetCustomer(memory: MemoryManager, customerId: string, ticketId: string) {
await memory.eraseAgentMemories(scopeFor(customerId), ticketId);
}from collections.abc import Awaitable, Callable
from actrone_memory import MemoryManager
CallModel = Callable[[str, list[str]], Awaitable[str]]
def scope_for(customer_id: str) -> str:
"""Long-term memory is scoped by agent_id, so one scope per customer keeps them apart."""
return f"customer:{customer_id}"
async def import_account(memory: MemoryManager, customer_id: str) -> None:
"""Seed what your CRM already knows, tagged with its source and how sensitive it is."""
scope = scope_for(customer_id)
await memory.inject_memory(
agent_id=scope,
content="The customer is on the Business plan and renews in March.",
importance=0.9,
session_id="crm",
topic_tags=["plan"],
source="import:crm",
sensitivity="none",
)
await memory.inject_memory(
agent_id=scope,
content="The customer billing email is dana@example.com.",
importance=0.6,
session_id="crm",
topic_tags=["contact"],
source="import:crm",
sensitivity="pii",
)
async def answer_ticket(
memory: MemoryManager, customer_id: str, ticket_id: str, question: str, call_model: CallModel
) -> str:
"""Answer a ticket with what you know about this customer, minus personal data."""
scope = scope_for(customer_id)
context = await memory.retrieve_context(
agent_id=scope, session_id=ticket_id, query=question, token_budget=2000
)
facts = [m.content for m in context.episodic_memories if m.sensitivity in ("none", "low")]
reply = await call_model(question, facts)
await memory.store_turn(
agent_id=scope, session_id=ticket_id, user_message=question, assistant_message=reply
)
return reply
async def forget_customer(memory: MemoryManager, customer_id: str, ticket_id: str) -> None:
"""A deletion request: erase the customer's memories and this ticket's turns."""
await memory.erase_agent_memories(scope_for(customer_id), session_id=ticket_id)Create the manager once and pass it in: await MemoryManager.create() in TypeScript, or async with create_memory_manager() as memory: in Python.
What this does not do for a support team
Tags come from you or from your extractor
injectMemorystores the tag you pass. Automatic tagging comes from fact extraction, which calls a model you configure. If that model is hosted, it sees the raw text, personal data included.It returns tags. It does not redact
Keeping personal data out of a prompt is the filter in your code. Nothing is masked or removed unless you do it.
Erasure covers these stores, not your copies
eraseAgentMemoriesdeletes from the stores you configured. Copies elsewhere, such as logs, analytics or a model provider’s retention, are yours to handle. Turns on the customer’s other tickets stay until you clear those sessions, or until they expire in Redis.Recall matches shared words until you add a model
With no extra packages the embedder compares words, so “Which plan is the customer on?” finds the Business plan fact because the words overlap. Install
fastembedin TypeScript or theonnxextra in Python for recall by meaning, still on your machine.A library, not a helpdesk
There is no Zendesk, Intercom or Freshdesk connector. You call it from your own agent code, next to whichever helpdesk API you use.
It does not make your process compliant
It gives you scoped storage, sensitivity tags and an erase call. Whether your support process meets GDPR or POPIA depends on everything around it.