# Memories: one user-owned store

Use the Decoy MCP memory tools (`npx decoy-mcp-server`) or the [HTTP memory contract](/docs/memory.md) at `https://api.decoys.me`. Inspect `tools/list` for exact inputs. Both set categories and a topic on every save.

Read memory before each new task. Save rich context throughout the task, not only at its end. Give every memory its categories and a topic. Correct it when it changes. The [five habits](#five-habits-for-every-task) below say exactly how. The person owns their memories and can review and change them in Decoy. Another agent can use those same memories when the person grants it access. The copied setup prompt asks for detailed proactive capture, including active work as well as lasting preferences; brief chat replies do not require brief stored memories.

**Do not create an account named Memories or choose a storage location.** Save a grounded, independently useful entry with enough context to understand it later; multiple sentences or paragraphs are welcome. Categories, topics and accounts describe what a memory is about; they are labels on one store, not separate vaults or agent-owned silos.

## Five habits for every task

These are operating rules, not suggestions. They apply on every task once Save memories is approved, without asking the person again.

1. **Read before you start.** Before a new task, look in Decoy for what is already known about its subject: search by the subject, and list the topics that match. Use what you find before asking the person anything it already answers. Memory is the store of knowledge about this person, their accounts, their preferences and their past decisions; a host's own local memory search is not a search of Decoy.
2. **Write as you go, not only at the end.** Save when you learn something worth keeping during the task: a decision, a preference, a fact about an account, an intermediate result, a dead end worth not repeating. Save at natural checkpoints (a finding, a choice, a completed step, before handing work to a helper) and again at the end. Sessions end abruptly; what was not saved is lost.
3. **Always organize.** Every memory gets every category that fits and one topic. Find and reuse an existing topic before creating one. When you create a topic, say how it connects: give the relation that is true for this person and this subject, in active voice and in their words, and hang it from an existing topic when it is part of one. The default connection is only for when nothing more specific is true ([connections](#connections)). A memory is about exactly one thing: a topic, or one of the person's accounts (then it is filed under that account instead), or the person. Keep the last for an isolated fact that belongs to no subject.
4. **Keep memories correct.** When a fact changes or was wrong, update the original memory by its ID; do not add a second memory that contradicts it. Delete what is wrong and cannot be corrected, within your access.
5. **Leave secrets out.** Passwords, API keys, sign-in codes and card numbers belong in the account's own fields, never in a memory's text, labels or metadata. Also leave out whole transcripts, raw tool logs, filler and guesses presented as facts.

**Naming a topic.** Use the person's own words for the subject as a short noun phrase: "Golf club headcovers", "Lisbon trip", "Home network". At most 200 characters, never the person themselves ("You", "me", "user"). Matching ignores case, spacing and quotes, so "lisbon trip" finds "Lisbon Trip"; reuse the stored name rather than a near-variant such as "Trip to Lisbon". A type is optional: `device`, `document`, `event`, `goal`, `hobby`, `media`, `organization`, `person`, `pet`, `place`, `product`, `project`, `team` or `trip`.

<a id="when-your-earlier-memories-are-not-readable-yet"></a>
**When your own earlier memories are not readable yet.** Right after a reconnect with a new key, a read lists memories you saved as `preparing`, and your topics are missing with them. Do not save blind: topics are reused by name among the ones you can read, so a save made now creates a topic you may already have. Wait, save once your topics are readable, and never let a task end with something silently unsaved: [the steps](https://www.decoys.me/agent-recipes.md#saving-while-your-earlier-memories-are-preparing).

### The calls, over HTTP

With [agent-memory.mjs](https://www.decoys.me/examples/agent-memory.mjs) on `https://api.decoys.me`. The server stores ciphertext and cannot search it, so reading is one GET followed by decrypting and filtering in your runtime. The [HTTP contract](https://www.decoys.me/docs/memory.md#read-first-write-as-you-go) has this as one runnable example.

| Habit | Call |
| --- | --- |
| Read before you start | `GET /api/agent/context/memories`, then print `summarizeRows(memories, privateKey).text` (the array holds memories, topics and links together), then `listTopics(memories, privateKey)`, `findTopic(topics, "Lisbon trip")` and `readMemoryRows(memories, privateKey, topics)` for the memories; keep the ones on that topic or whose text or categories match the task |
| Write as you go | `POST /api/agent/context/memories` with `rows: [newMemoryRow({ text, categories: ["travel", "user_preferences"], topic }, keys)]`. The topic is the `topic` argument (`{id, name}` from `findTopic` or `newTopicRows`); a topic named in `metadata` files nothing and is refused |
| New topic | `newTopicRows({ name, type, link: { relationType, topic? } }, keys)`; POST its rows in the same request as the first memory filed under it. `link` says how the topic [connects](#connections): `relationType` is the relation that is true for this person and this subject, and `topic` is the existing topic it is part of, when there is one |
| About an account | `newMemoryRow({ text, categories, accountId }, keys)` instead of `topic` |
| Correct | `PUT /api/agent/context/memories` with `editMemoryRow(served, { text }, privateKey, keys)`; the same call with `{ topic }` or `{ categories }` files a memory you saved earlier |
| Delete | `POST /api/agent/context/memories/delete` with `{ "ids": ["<memory id>"] }`; check `deleted` and `refused` |

### The calls, with the MCP bridge

| Habit | Call |
| --- | --- |
| Read before you start | `search_memories({"query":"Lisbon trip flights"})` for the subject, and `list_topics({"query":"Lisbon"})` for the topics that bear on it. `search_memories({"query":"flights","topic":"Lisbon trip"})` or `get_memories({"topic":"Lisbon trip"})` reads everything filed under one topic |
| Write as you go | `add_memory({"text":"Lisbon trip: direct morning flights run on Tuesdays and Fridays only (checked 2026-10-05). Status: no flight chosen yet.","categories":["travel"],"topic":"Lisbon trip","topic_type":"trip","topic_link":{"relation":"is planning"}})` |
| Topic | `list_topics` first. Pass the existing topic's name as `topic`; a topic you can read is reused whatever the case, spacing or quotes, and one that does not exist is created with the memory. `topic_type` and `topic_link` are used only when the topic is created; `topic_link` says how it [connects](#connections) |
| Part of an existing topic | `add_memory({"text":"Lisbon trip: staying at Hotel Example, 3 to 8 May. Late check-in is arranged; a quiet room was requested.","categories":["travel"],"topic":"Hotel Example","topic_type":"place","topic_link":{"to":"Lisbon trip","relation":"is based at"}})` |
| About an account | `add_memory` with `app_id` set to the account's name or ID, instead of `topic` |
| Correct | `update_memory({"memory_id":"<returned ID>","text":"...","expected_rev":<rev from your read>})`. The same tool files an existing memory: `update_memory({"memory_id":"<returned ID>","topic":"Lisbon trip","categories":["travel"]})`; `"topic":null` files it back under the person |
| Delete | `delete_memory({"memory_id":"<returned ID>"})` |

One `add_memory` call sets the text, every category, the topic and, for a new topic, its connection. Results carry each memory's `topic`, `categories` and `rev`; pass that `rev` as `expected_rev` when you update, so an edit made since your read is not overwritten. On a memory another connection saved, Read & write lets you correct the text only; its topic and categories stay its author's.

### A task, start to finish

A synthetic example, not a fact about the reader. The person asks: "Find me a flight to Lisbon for the first week of May."

1. **Before searching flights**, the agent looks up "Lisbon trip" in Decoy. It finds the topic with two memories: "Prefers aisle seats and morning departures" and "Hotel booked 3 to 8 May". It uses both; it does not ask about seat or dates.
2. **During the search**, it learns that direct flights run only on Tuesdays and Fridays, and that the Thursday option needs two connections. It saves that now, under the Lisbon trip topic with category `travel`, marked as an intermediate result with the date checked.
3. **The person picks the Friday 08:10 flight.** The agent updates that same memory's status line rather than adding a new one, and saves the decision and its reason.
4. **The password for the airline login** it creates goes on that account in Decoy, not into a memory. The memory records only that the account exists and what it is for.
5. **The hotel becomes a subject of its own** (check-in time, room request, a parking question). The agent creates a topic for it hung from the Lisbon trip topic, with a relation that says how the two relate (the trip is based at that hotel), not a second topic hanging from the person by the default.
6. **Next week**, a different agent the person has granted this topic reads the same memories before suggesting airport transfers.

## Capture generously; structure as you save

With the person's detailed-memory preference adopted and saving approved, capture newly learned useful context at each substantive exchange and meaningful work milestone. Save during the task, not only at setup or the end of a conversation. Do not wait for "remember this," limit Decoy to lifelong traits, or impose a small starting quota. This is work within the active session, not permission to schedule a background collector.

Include the details another authorized agent would need to pick up the work: stated preferences and requirements, constraints, options actually considered, decisions and reasons, grounded findings and sources, unresolved questions, progress, outcomes and next steps. Temporary context belongs too, with its actual task/time scope. Distinguish the person's choice from an agent recommendation, a candidate from a purchase, and a research result from an established personal fact. Do not invent dates, motives or missing details.

Structure every useful entry, not just the overall store. Use a clear subject plus short headings or labeled lines inside the existing text field when helpful. For example: **Preference**, **Constraints**, **Decision and reason**, **Evidence and date**, **Status**, **Next step**. Include only applicable, known parts; there is no fixed template to fill or new API schema. Preserve coherent detail with its qualifiers. Split independently useful subjects, but do not fragment every sentence or pack unrelated subjects into one huge note.

Before each save, check whether the new information enriches or corrects an existing accessible record. Update that record when appropriate; create a new one for independent information. No change means no duplicate write. Rich capture is measured by useful context retained and kept accurate, not by maximizing row count. Keep credentials in account records; skip filler, whole transcripts, raw tool logs and private internal deliberation. Existing exclusions, source-import rules and destination-aware consent for sensitive facts still apply.

Apply categories and real topic associations using the workflow below. Verify the actual stored content and organization. Routine writes should stay quiet, with brief truthful confirmations for direct remember/correct/forget requests or a material failure.

## Save, find, correct and forget

The MCP bridge exposes seven memory tools by default (for HTTP use the [direct contract](https://www.decoys.me/docs/memory.md)). These are MCP calls, not plaintext REST bodies:

| Task | Tool | Arguments |
| --- | --- | --- |
| Save | `add_memory` | `{"text":"Prefers concise, practical replies.","categories":["user_preferences"],"topic":"Communication preferences","topic_link":{"relation":"asks agents to follow"}}`; `topic_link` applies only when the topic is new |
| Recall relevant context | `search_memories` | `{"query":"communication preferences"}`; add `"topic":"<name>"` to stay within one topic |
| List topics | `list_topics` | `{}` or `{"query":"communication"}` |
| Browse | `get_memories` | `{"page":1,"page_size":10}`; add `"topic":"<name>"` for one topic |
| Read one | `get_memory` | `{"memory_id":"<returned ID>"}` |
| Correct or refile | `update_memory` | `{"memory_id":"<returned ID>","text":"Prefers concise replies with examples when useful.","expected_rev":1}`; `topic` and `categories` change its filing |
| Forget | `delete_memory` | `{"memory_id":"<returned ID>"}` |

Keep the actual returned IDs. Read the tool response, including `isError`, before claiming success. A save can report an existing duplicate rather than a new memory. Reconcile uncertain outcomes through a read before retrying. A fresh process can verify API read-back using the same securely stored connection and key. To establish ongoing behavior, separately observe a fresh agent session discovering and using the memory in a relevant task without the original chat; record prompted retrieval separately from automatic use. Chat history is not evidence of retrieval.

Saving requires the **Save memories** permission, `{"kind":"memory","actions":["create"]}`, for writes to the shared user-owned store; each connection can read only its authorized records. The bridge asks for it when it connects. To know whether you can save or read, check `memories.save` and `memories.read` in [`access`](https://www.decoys.me/docs/access.md#what-you-can-do-now), never `scopes`: no scope covers memories. If `add_memory` returns `INSUFFICIENT_GRANT`, ask once with `request_access({ kind: "memory", action: "create" })`, then retry. Save memories needs no account or Personal Info grant and opens no one else's memories. Agents can read, correct and delete what they saved. Other memories require a matching grant: Read to retrieve, Read & write to correct the text, Full access to delete. Changing a memory's topic or categories is for the author or the owner only; owner clients must rely on trustworthy edit provenance before sharing labels. Account access brings the memories about that account. Memory-only connections need no account or email scope.

For a service-specific fact, optionally pass `app_id` with the account name or ID. A matching granted account becomes an about-account label; an unresolved value remains a hint. A missing account match does not block saving or create an account. Do not ask the person to choose a place, invent account IDs or broaden access just to store a preference.

`user_id` is the connection's owner (`default_user`); callers cannot switch users. `agent_id` is a filter convention, not an authorization boundary. `run_id` does not create a session-specific store. `categories` accepts the documented mem0 categories; the phone categorizes untagged memories. Search and duplicate checks run in the bridge's runtime. Do not treat a category filter as an access grant.

## Correct an existing memory

Rich capture maintains detailed, accurate current records. Before writing, find and read the relevant existing memory; use its returned ID and the current stored content. Do not interpret every message as a new record.

| What changed? | What to save |
| --- | --- |
| An earlier fact was wrong, or the person refines that same fact | Update the original record in place. State the correct fact directly; remove the erroneous detail while preserving unrelated useful content and labels. Do not retain the false value as history or add a standalone "Correction:" memory. |
| Something genuinely changed over time | Update the current state. Retain the former state only when it was true and remains useful, clearly marked with its known date or scope; never invent a date. A distinct historical event may have its own record without remaining a competing current fact. |
| A different project, scoped exception or new independent fact | Update a matching scoped record if one exists, or create a separate entry with that scope. Do not overwrite a general preference with a one-off exception. |
| A known original is inaccessible, or editing is refused | Apply the person's current correction in the task and report the actual save limitation. Do not create a contradictory replacement, infer that the original is absent, or claim it was updated. |

**Worked example — synthetic, not a fact about the reader.** The returned record ID is `memory-example-1` and its text is "Paper Kite phase-one budget is $200. Use untreated timber because it will be next to edible plants." The person says: "The budget was $300 all along; keep the timber requirement." Read that record, then use:

```json
{"memory_id":"memory-example-1","text":"Paper Kite phase-one budget is $300. Use untreated timber because it will be next to edible plants."}
```

Those are arguments to MCP `update_memory`, not a REST body. Use the actual returned ID, not the illustrative ID. Over HTTP, read the latest served row, use `editMemoryRow(served, correctedText, privateKey)` and send its encrypted result to `PUT /api/agent/context/memories`. This keeps the row ID, key, labels and metadata and supplies `expected_rev`. Do not send plaintext text to that route. With the bridge, pass the `rev` from your read as `expected_rev` to `update_memory`. The same call changes categories or attaches, moves or detaches a topic on that record (see [topics](#categories-and-topics)).

**Bind a correction to what you read.** Both transports take the revision you read: `expected_rev` on `update_memory`, and on the HTTP `PUT` (which `editMemoryRow` fills in). When the memory changed since your read, nothing is written and you are told to re-read: read it again, re-apply the person's correction to the current text and retry. Without `expected_rev`, `update_memory` writes over whatever is there; always pass it for a read/modify/write correction. A later read-back cannot prove no edit was lost.

Read the same ID back through the supported tool/API. Verify the corrected text, preserved details and existing labels before reporting success. On a revision conflict, re-read and reconcile the person's correction with concurrent owner edits; never retry a stale whole-record replacement. On a timeout or unknown write outcome, read the known ID before retrying. Do not create a second record as a fallback. A suitable brief confirmation is "Updated the project budget in Decoy." Routine corrections within existing authority need no further approval.

If earlier behavior has already produced a separate "Correction:" entry, reconcile the relevant accessible originals and correction entries. First verify one complete corrected record. Remove a redundant copy only when that deletion is authorized; preserve independent facts and meaningful history. If any competing record cannot be read, edited or deleted, say which part remains unresolved without claiming complete cleanup. Never create a fresh "master" copy merely to work around access limits.

An edit-error message suggesting that you save the corrected fact as a new memory is not an appropriate fallback for a known existing record. Preserve the current correction for the task, report the refused update, and use the existing connection's documented access path when additional access is actually needed.

Keep separate counts for created and updated records in evaluations. A correct sentence in a new record is not an in-place-correction pass. Require the original ID with the corrected content, preserved unrelated details/labels, and no new contradictory copy.

## Categories and topics

Organize as you save. Use relevant labels without asking the person to design a taxonomy. Read saved content and labels back; a successful call does not prove every requested label was retained.

**Categories** are supported values, not arbitrary tags:

`personal_details`, `family`, `professional_details`, `sports`, `travel`, `food`, `music`, `health`, `technology`, `hobbies`, `fashion`, `entertainment`, `milestones`, `user_preferences`, `misc`.

Apply every relevant category through `add_memory` or the HTTP helper's `categories` input; a memory keeps all of them. Use `misc` when no specific category fits; do not tag every category for discoverability. Labels can make a record available to connections holding the corresponding live grant, so accuracy matters. The phone can categorize untagged memories. Verify the returned categories instead of assuming a runtime preserved all of them.

The following is a synthetic example, not a fact about the reader. These arguments are for the MCP tool; over HTTP pass text and categories to `newMemoryRow` in the [helper](https://www.decoys.me/examples/agent-memory.mjs) and send only the resulting encrypted row:

```json
{"text":"Paper Kite garden project: the user chose raised beds because the courtyard soil is poor. For this project, keep the first phase under $300 and use the sunny east wall. This is a project constraint, not a general spending preference. Next step: compare untreated timber sizes before ordering; nothing has been bought.","categories":["hobbies"]}
```

**Topics** are the subjects the person keeps coming back to: projects, trips, purchase decisions, hobbies, people or organizations. The person sees each one under Topics and on the Map, with its memories, joined to You or to the topic it is part of by the [connection](#connections) you gave it. A topic is a real stored object; a topic name in the text, in `metadata` or in an unresolved `app_id` hint is not one.

### When to make a topic

- **Reuse first.** Look for an accessible topic with the same name before creating one, and use its current name. Later conversations and other authorized agents should land on the same topic; preserve the person's renames and edits.
- **Create a topic for a developed subject**, without waiting to be asked: repeated discussion, an active research or purchase decision, or several related useful memories. Not for every noun, product mention or conversation. A broader parent (such as **Golf**) is worth linking only if it already exists or the conversation justifies it; no hierarchy is required.
- **Capture useful detail generously while the conversation happens**: preferences, requirements and constraints, the options actually considered, decisions and their reasons, outcomes, open questions and next steps. Give each memory a clear subject and labeled lines. Enrich an existing memory on the topic rather than adding a near-copy.
- **Corrections update the same memory by id**, keeping its topic and categories. Never create a "Correction:" copy.
- **Never duplicate a memory to relabel it.** Attaching an existing memory to a topic, moving it, or detaching it is an edit of that record.
- **Keep chat brief.** The stored context should be detailed; the conversation should not narrate the filing.

**Headcovers example — synthetic.** Someone discusses which golf club headcovers to buy over several turns: club fit, style preferences, two options compared, a choice and why. Without asking how to organize it, the agent looks for an accessible **Golf club headcovers** topic and creates it only if none exists, connected by what is true in this conversation: the person is choosing a set, so the relation says that. It saves a fit-requirements memory, a style-preference memory and a decision memory (options, reason, status, next step), each with its categories and attached to that topic. Later, when the person adds a club, the agent updates the fit memory by its id; the topic and categories stay. A later session or another authorized agent finds the same topic and adds to it.

### Connections

A connection is the line on the Map that says how a topic relates to the person, or to another topic. It reads as a sentence from its source to the topic: "You" (or the parent topic), the relation, the topic's name. A new topic gets exactly one, when it is created, so choose it then. Left out, it is "is interested in" from the person ("includes" from a parent topic). That default is a fallback, not a choice: a Map where every line says the same thing tells the person nothing.

- **Ask what is true.** What is this person's actual relationship to this subject right now? Do they own it, plan it, decide between options, work there, maintain it, pay for it, teach it, learn it? Those are kinds of answer, not a list to pick from. Write the relation this conversation establishes, and do not copy one of these, an example on this page or the default unless it is the truth.
- **Write it as the person would say it.** Active voice, third person, present tense, a few words, at most 100 characters. It must read as a true sentence with the source before it and the topic after it.
- **Hang a part from its whole.** When the new topic is part of a subject that already has a topic (a hotel within a trip, one shortlisted model within a purchase decision, a course within a degree), connect it from that topic, not from the person, with a relation that says how the part relates. Do not create a parent only to have one.
- **Do not invent.** Use what the person said or what the conversation clearly shows. When you cannot tell how they relate to the subject, leave the connection out and the default applies.
- **Keep it a label.** A relation is shown on the Map and is read by every connection the person grants the topic. No secrets, identifiers, amounts or sensitive detail; those belong in a memory's text or on the account. For a health, money, legal or relationship subject, the relation must reveal no more than the topic's name already does: use a neutral one, or the default, and keep the detail in the memory.

Over HTTP: `newTopicRows({ name, type, link: { relationType, topic? } }, keys)`. With the MCP bridge: `topic_link: { relation, to? }` on `add_memory` (and on `update_memory` when its `topic` is created), where `to` is an existing topic's name or `topic:<id>`. The bridge uses `topic_link` only when it creates the topic; an existing topic keeps its connection. With the optional graph tools, the same care applies to `relationType` in `create_relations`.

### Topics over HTTP

The [direct HTTP contract](https://www.decoys.me/docs/memory.md#topics) has the exact rows and a runnable version of the headcovers example. With the [helper](https://www.decoys.me/examples/agent-memory.mjs):

1. `listTopics(memories, privateKey)` lists the topics this connection can see, oldest first: `[{id, name, type, memories, ids}]`. It is worked out from the rows you can open; the server is not asked and learns nothing. Same-name topics are listed once, under the oldest id, as the app shows them; `sameTopic(a, b)` treats them as one.
2. `findTopic(topics, name)` reuses the oldest topic whose normalized name matches exactly (case, spacing, quotes and Unicode forms ignored). Same-name topics are not merged in storage.
3. If there is none, `newTopicRows({name, type?, link?}, keys)` (type one of the common types such as `product`, `project`, `trip` or `person`, or left out) returns the topic's entity row and its one relation: from You, or from an existing topic with `link.topic`, reading `link.relationType` (see [connections](#connections)). POST them **with** the first `newMemoryRow({..., topic})` rows in one request, to associate the topic with its first memories. If the outcome is unknown, POST the same sealed rows again; the server stores each id once and read-back must confirm the save. Never build fresh rows for a retry.
   - Filing an existing memory under a new topic: POST the topic rows, then PUT the memory; if that PUT is definitely refused (403, 404, `REV_CONFLICT`, `KID_CONFLICT`: `isDefiniteRefusal`), delete the topic rows again, best effort. After a network error, a timeout or a 5xx it may have committed: keep the topic, re-read the memory to learn the outcome, and report it.
   - In an add flow, when the memory already exists, `attachDuplicate(served, {topic, categories})` files it under the topic only if this connection saved it. Someone else's memory (another agent's or the owner's) is reported as a duplicate and never relabelled by an add.
4. `readMemoryRow(served, privateKey, topics)` returns each memory's `topic` (`{id, name, type}` or null), all its categories and its `rev`. For a row that is a topic or a link it returns null: that row is not a memory, and it is not unreadable. `summarizeRows(memories, privateKey)` counts the array as `{memories, topics, links, unreadable}`, with the ids of the unreadable rows; [print it first and check where each memory landed](https://www.decoys.me/agent-recipes.md#read-the-array-then-check-where-a-memory-landed).
5. `editMemoryRow(served, {text?, categories?, topic?}, privateKey, keys)` changes one memory in place, same id. `topic: {id, name}` attaches or moves it; `topic: null` returns it to You; naming its current topic in any spelling keeps it where it is. Only the author or the owner relabels: on someone else's memory, Read & write corrects the text and leaves topic and categories exactly as they are (the helper refuses a relabel). Only author- or owner-approved labels may determine new sharing. This requires matching server edit provenance and owner-client enforcement; the HTTP helper alone cannot establish that protection for historical edits. When you remove a label from a memory you saved, it rotates the key (pass `keys` from `memory-grantees`). On `409 REV_CONFLICT`, re-read and reconcile.

A memory has exactly one topic, account or the user. Access works as for any memory: a topic grant reaches the topic and its memories, and a memory you can read reveals its topic's name.

### Topics over MCP

The default memory tools handle topics directly:

1. `list_topics({"query":"Paper Kite"})` lists the topics this connection can read. Reuse one that fits before creating another.
2. `add_memory({"text":"...","categories":["hobbies"],"topic":"Paper Kite","topic_type":"project","topic_link":{"relation":"is building"}})` files the memory under that topic. A readable topic with that name is reused, ignoring case, spacing and quotes; otherwise the topic is created in the same request. `topic` also accepts `topic:<id>`. `topic_link` says how a new topic [connects](#connections): `relation` is what is true for this person and this subject, and `to` names an existing topic when the new one is part of it. Leave it out only when nothing more specific than the default is true.
3. `update_memory({"memory_id":"<returned ID>","topic":"Paper Kite"})` files a memory you saved earlier under a topic, in place; `"topic":null` files it back under the person. Never save a copy to relabel a memory.
4. `search_memories` and `get_memories` take `topic` to read one topic's memories; every result carries its `topic`.

When a save matches one of this connection's own memories that has no topic yet, the bridge files that memory under the topic in place (the result says `UPDATE`, same ID) instead of adding a duplicate. A match with a memory someone else saved is reported as a duplicate and left unchanged.

The MCP Memory graph tools (`search_nodes`, `create_entities`, `add_observations`, `create_relations` and the rest) remain available with `DECOY_MEMORY_GRAPH=true` for clients that need that contract, over the same store. They take no `categories`. Prefer the default tools above; they set both labels in one call. In `create_relations`, choose each `relationType` with the same care as a topic's [connection](#connections). A topic name in text or metadata is not a topic. Do not invent an argument, ask the user to build a taxonomy or claim a topic was created without reading it back.

`update_memory` with only `text` keeps existing labels and metadata; with `topic` or `categories` it relabels the same record, as `editMemoryRow` does over HTTP. Do not duplicate an existing memory just to change its organization.

**One store, one copy of each piece of information.** A graph observation is already stored memory. Do not also create a categorized simple-memory copy of it, or copy an existing simple memory into an observation merely to make a topic appear. Attach the existing record (`update_memory` with `topic`, or `editMemoryRow` over HTTP). Corrections to topic memories follow the same original-ID update rule as other memories. Graph add/delete calls do not constitute an in-place edit; if the original memory ID or supported edit path cannot be obtained, do not delete/recreate or append a contradictory observation as a substitute.

## First useful action and ongoing use

Memories belong to the person, so what one agent learns can be available to the next agent they authorize. With the adopted detailed-memory preference and Save memories permission, proactively save preferences, instructions, plans, decisions, constraints, research findings, meaningful progress, outcomes and next steps without asking each time. They can see, edit and remove entries in Decoy. Distinguish what the person stated, what a source establishes and what remains uncertain; never turn guesses into personal facts.

On the first connection, capture the useful grounded context already available in the conversation, reusing accessible equivalents. The setup prompt's stated preferences are enough when there is no richer context, but are not a two-memory cap. Preserve reasons, constraints, relevant dates and next steps in independently useful entries. Do not invent a profile or ask for starter facts again.

Read each saved result through Decoy, then say what succeeded and invite the person to open the app to review or change it. Use only documented or returned links. Do not require an app inspection to finish setup or claim that agent read-back proves owner-app readability.

After that first result, if exposed personal saved memories would add value, offer a larger backfill with an accurate count, a short non-sensitive sample and Decoy as the explicit destination. Obtain one approval for that batch unless the exact import is already authorized. Apply known exclusions first; do not harvest transcripts, scan unrelated files, copy employer/project material or reconstruct hidden memory. Sensitive facts require destination-aware consent. Skip possibly excluded material when source exclusions are unavailable. Import is optional.

Write or update after substantive exchanges, new instructions, useful findings, decisions, progress, corrections and completion. Favor including grounded context another agent could use. Temporary work belongs too: date and scope it instead of dropping it or generalizing it into a permanent preference. Keep source uncertainty and dates distinct from import time. Preserve concise decision rationale and human-readable working context, not private internal deliberation, raw tool logs, filler or whole transcripts. Credentials go in their Decoy account records.

Decoy should not be narrower than a host's local memory by default. Useful active-work context belongs in the shared store; local caches can coexist where the host needs them. Keep returned IDs and reconcile owner edits instead of making drifting copies. Read before editing and respect access refusals and exclusions. A one-off instruction can be recorded with its scope without replacing an enduring preference. Do not ask again whether to use this logging policy; it is already stated in the copied setup prompt.

Follow the [ongoing-use guide](/agent-operating-guide.md) for operating preferences, private host reminders and forgetting. An operating note is an ordinary encrypted memory convention, not a privileged API object. Another agent needs a matching grant and shared key to see it. There is no documented universal exclusion registry: do not promise that deletion prevents every host from recreating a fact. Do not retain forgotten content inside a suppression note, and skip an affected import source when exclusions cannot be checked.

Keep routine operations quiet. Confirm direct remember/correct/forget requests and explain only limitations that affect the result. The [experience guide](/agent-experience.md) governs the conversation.

## What opens other memories

Use [access levels](/docs/access.md). A memory row can name categories through `datatypes`, topics through `about: ["topic:<id>"]`, or all memories with neither selector. Categories and topics go in separate rows. The MCP `request_access` tool can resolve known topic names; HTTP requests use topic IDs.

Grants are live sets. New memories matching the approved category/topic/account become available when their keys have been shared. The phone handles classification and key sharing; approval supplies an initial batch of up to 50 keys and shares the rest afterward. Temporary absence does not prove there are no memories. Re-read or follow sync; do not re-pair to force delivery.

## Privacy and control

Memory text and labels are encrypted in the integrating runtime. Decoy cannot read those values; your agent/provider can read the values shared with it. The server sees operational metadata such as IDs, authorship, revisions, recipients and access reasons (`via`); do not claim it sees no metadata or cannot infer a grant class.

Narrowing or revoking a grant stops future serving. It does not erase cached keys, envelopes or plaintext already received. Forgetting a stored memory is not deletion of provider logs or past conversation copies.

The owner can edit or remove memories in the app. Audit activity records content-free actions; sync and webhooks disclose only changes the connection may receive. Follow [sync](/docs/sync.md).

## Direct HTTP and graph tools

Most integrations should use the memory tools above and let the bridge handle encryption. The optional MCP Memory graph tools operate over the same store with `DECOY_MEMORY_GRAPH=true`; they are not the default tool list. `memory://knowledge-graph` exposes the visible graph. Categories, graph topics and account labels do not create separate stores.

Use the per-memory endpoints and local encryption helper in the [HTTP contract](/docs/memory.md). Send the encrypted memory row with its owner, self and applicable grantee wraps. Read the saved row independently and decrypt it to verify the content.

[MCP quickstart](/docs/memory-quickstart.md) · [Access guide](/docs/access.md)
