Skip to content

Memory

Memory is what an agent keeps from one conversation to the next: a fact about a customer, a preference, a promise, the way a task is done. Core has no separate service for it. Each kind of memory lives in a part of Core you already know, and for each part the same question applies: does this belong to one person, or to the whole organisation?

What Core remembers, and where

What you want remembered Where it lives Who writes it
Facts and preferences, such as "prefers email to phone" memory records in a memory store, with a type you choose your application with POST /memory, the model with the write tool, or Core itself from your conversations
Rules for how the assistant should act for one person, such as "reply in Dutch" memory records of type behaviour, hers alone, given to every turn for her the same three
Commitments, such as "calls the bank back on Thursday" memory records of type commitment, with a status, a due time and who it is owed to the same three
How a task is done skills, an agent's instructions and prompts you or an administrator, in the organisation's library
What happened in a conversation the conversation itself, with a summary once it grows long Core, on every turn
What happened in past sessions memory records of type episode, one for each settled session, found again by what a new turn is about Core itself, from your conversations, when the store's extraction asks for it (Keep a record of each session)
What a person's own sessions taught memory records of type lesson, hers alone Core itself, from her own alike sessions, when the store asks for it (Let a person's sessions teach her assistant)
The task in hand: its goal, the decisions taken, the next step the conversation's working state, one small JSON object shown at the start of every turn, see Keep a working state the model with builtin__update_working_state, or your application

Every write, the model's and extraction's included, needs a rule in the key's policy that permits memory_write; the default rules hold none (Write your first rule).

A turn gets memory in two ways. The model can search with a memory store's search tool. Or Core adds matching records to the context before the model starts, when you switch that on for the key. To the model an added record is always data, never an instruction. How to set up both is in Memory through the API.

Whose record it is: a person's or the organisation's

A memory store holds two kinds of records side by side:

  • Personal records. When a request names a person, with X-Core-Subject, a subject token or the user field, every record it writes belongs to that person. "Jan prefers mornings" is Jan's.
  • Shared records. A request that names nobody writes a record that belongs to the organisation. "Expense claims go to finance by the 25th" is everyone's.

What Core extracts from conversations follows the conversation: a conversation of Jan's gives Jan's records. By default extraction reads only conversations that name a person (threads is with_person), so a conversation that names nobody gives no records at all.

Under a key's default rules a person sees her own records and the shared ones. Piet never sees Jan's records: not in a list, not in a search result and not in what Core adds automatically, and a read by id answers 404. A request that names nobody, such as a background job of your application, reaches every record. What Core adds automatically is narrower: a turn for Jan gets his own records and the shared ones, in separate sections of one block, and when the store asks for them his own past sessions under [past_sessions], so many shared records never crowd out his own, and a turn that names nobody gets the shared ones only. Every record shows its confidence as a percentage, and the block is held to a token budget: the key's memory.injection.budget_tokens (1,500 by default), which a store's optional injection.budget_tokens and your operator can only lower. An agent can bring its own injection block, with its own store and budget, which then replaces the key's for its turns.

A personal record never becomes shared by itself. A correction keeps the fact with the same owner, and Jan's subject token cannot change or delete a shared record (403).

Always name the person. A request that names nobody counts as a request of the organisation. If your application forgets to send the person, Jan's conversation becomes a conversation for nobody. With threads: with_person, the default, Core does not mine that conversation. A switch whose GET shows threads: all still mines conversations for nobody, so his personal facts become shared records that all his colleagues are given; a PUT without threads sets with_person. Set settings.subjects_required to true in the policy of every key that works for people, and a request without a person is refused (What a request without a subject reaches). You can also limit extraction with filters.key_ids to the keys that always name a person.

Departments: labels on shared records

Core has no departments. You make them with a label. A shared record for payroll carries "labels": {"department": "payroll"}, and a shared record for everyone carries "labels": {"audience": "everyone"}. A shared record the improvement loop proposes starts with the store's promotion.labels in its metadata, which these rules read only during the move to labels. The person carries their department as an attribute on the request, for example department: ["payroll"].

In the key's policy you then replace the default rule memory-record-shared with two rules that permit shared records only:

{"id": "memory-record-everyone", "effect": "permit", "kind": "memory_record",
 "match": {"labels": {"subject": "", "audience": "everyone"}}},
{"id": "memory-record-department", "effect": "permit", "kind": "memory_record",
 "match": {"labels": {"subject": ""}},
 "when": [{"atom": "label_among", "label": "department", "attribute": "department"}]}

"subject": "" means a record nobody in particular wrote. The rules memory-record-nobody-named and memory-record-own stay. Anna from payroll then gets her own records, the records for everyone and payroll's. Bob from reception gets his own records and the records for everyone. That holds for lists, searches and automatic injection alike.

Four things to watch:

  • A rule cannot say "everything except". A record for the whole organisation must therefore carry audience: everyone. A shared record with neither label is reached only by a request that names nobody. Records Core extracts from conversations carry neither label, so a shared record extracted from a conversation for nobody (only possible with threads: all) is reached only by such a request too.
  • Both rules are about shared records only. A label somebody puts on her own record does not show that record to colleagues.
  • A schedule never carries attributes. When it names a person, its turn gets that person's records and the records for everyone, and no department's. When it names nobody, its turn gets every shared record, every department's included.
  • If your organisation declared a vocabulary, declare department for memory_record with attribute: true, and audience for memory_record.

You divide skills by department the same way: a skill labelled department: payroll under a rule with label_among over skill is offered to that department only. How to write a rule is in Write your first rule.

Erase a person

DELETE /subjects/{id} erases a person's conversations and all her memory records, of every type (Erase a person). Shared records stay: they belong to the organisation and name nobody.

Read on