Use durable memory for a fact, preference or decision that should remain useful beyond one conversation. This guide saves a review rule for a small pilot study, retrieves it in another session, then updates and deletes it. It is for users who have completed Getting started .
Context and compaction explains why a durable memory record, its injected digest and the model’s current request are different. Use Manage a long session when a detail needs to be recovered after summarization.
The workflow targets Sonny 0.12.7 on Linux. Qualification used the installed binary, synthetic inputs and a controlled local provider. It exercised real tool calls and files; it does not establish live-provider reasoning quality or synchronization between machines.
Choose what to remember
A useful record is focused, named clearly and understandable without its original conversation. For example: “When comparing the pilot notes, cite both sources and show both denominators.” Keep the original evidence available so a later user can check the rule. Do not save passwords, API keys or private tokens as memories.
These similarly named things have different lifecycles:
| Material | What it preserves |
|---|---|
| Durable memory | A YAML fact, preference, decision or reference in a selected project or global scope. |
| Session history | The conversation and its recorded results. Deleting a memory does not retract copies already in a conversation or export. |
| Context and compaction | Material selected or summarized for a model request. A summary does not become a durable memory record merely because it survives a turn. |
| Memory search index | A derived SQLite FTS cache. The YAML files remain authoritative. |
| Lanning knowledge | Data held by a separately configured service, under that service’s scope and access controls. Local records are not automatically synchronized there. |
| Recipe policy-evidence retrieval | A separate retrieval path controlled by SONNY_MEMORY=on, a Recipe’s memory: policy and a configured retriever. |
Prepare a disposable project
Use a new directory and an approved model. This example uses the cookbook’s public review Recipe. Download and inspect it before starting:
mkdir sonny-memory-review
cd sonny-memory-review
mkdir -m 700 .sonny
mkdir approved-recipes
curl --fail --location https://sonny.sh/examples/cookbook/review-work.yaml --output approved-recipes/review-work.yaml
sonny recipe validate ./approved-recipes/review-work.yaml
sonny isolation doctor
sonny run --new --file ./approved-recipes/review-work.yaml --no-trace
The run creates a session and can create Sonny’s standard configuration/data/cache
state. Creating this project’s own .sonny directory makes it the default
project write scope; the memory tools create its memory/ subdirectory. Review the reported isolation and
honor any policy refusal. The Recipe itself does not restrict the tool surface;
your admitted tool policy, normal approvals and execution boundary still apply.
The provider receives prompts and retrieved memory contents.
The six memory operations are model tools, not shell subcommands:
| Tool | Result to inspect |
|---|---|
memory_store | Saved identifier, selected scope and whether a record was created or updated. |
memory_retrieve | Full record and the scope it came from. |
memory_list | Identifiers, titles, categories, tags and scopes, without bodies. |
memory_search | Ranked matches with scopes and content snippets. |
memory_delete | The identifier and the single scope from which it was removed. |
memory_stats | The scope chain and its record counts. Counts across scopes can include repeated identifiers. |
If an operation asks for approval, read the tool, identifier, scope and proposed content first. Use Allow once for that reviewed operation. The TUI deliberately ignores an immediate approval key while its dialog is arming; read it before responding. A tool being discoverable does not establish permission to run it.
Save and inspect a rule
Ask Sonny:
Use memory_store to save a project memory:
id: pilot-review-rule
title: Pilot comparison review rule
category: decision
tags: pilot, review
content: Cite Notes A and B; show both denominators; do not infer satisfaction.
Then show the saved identifier, scope and whether it was newly created.
Approve the specific write after checking it. The expected result includes
pilot-review-rule, created: true and a project scope ending in this directory’s
.sonny. If the returned scope names an ancestor instead, inspect the scope chain
before continuing.
The tool writes one YAML file per record. In this fresh project, inspect
.sonny/memory/pilot-review-rule.yaml; the qualified record had this shape:
id: pilot-review-rule
category: decision
title: Pilot comparison review rule
content: Cite Notes A and B; show both denominators; do not infer satisfaction.
tags:
- pilot
- review
source: agent
created_at: 2026-10-25T03:07:29Z
updated_at: 2026-10-25T03:07:29Z
Your timestamps will differ. title and content are required tool inputs.
An explicit identifier must match ^[a-z0-9][a-z0-9-]{0,63}$; if omitted, Sonny
can derive one from the title. category is free-form and defaults to fact.
The entire encoded record is limited to 1 MiB, not just its content field.
New memory directories use mode 0700 and record files use 0600 on the tested
Linux path. These permissions do not encrypt the contents.
Retrieve it in a new session
Close the first TUI session and run the same command again with --new, from the
same project directory. The new session has a different identifier. Ask:
Use memory_retrieve to fetch pilot-review-rule. Show its complete content and scope.
Do not infer the rule from an earlier conversation.
Inspect the actual tool result and approve that read if requested. It should
return the saved text and this project’s scope. A remembered-sounding answer
without a retrieval result does not prove persistence. You can ask for
memory_list or memory_stats to check which records and directories are visible.
Update and delete deliberately
To update the same record, ask:
Update project memory pilot-review-rule with memory_store.
Keep its title. Replace the content with:
Cite Notes A and B, show both denominators, and retain missing survey evidence.
Retrieve it again so I can inspect the stored result.
Check that the identifier and scope stayed the same and created is false.
An update preserves the original creation time and writes a new update time.
Omitting a category or supplying an empty tag list retains existing metadata in
this release; inspect the resulting record rather than assuming those values
were cleared.
When the rule is no longer wanted, ask:
Delete pilot-review-rule from project scope using memory_delete.
Show the scope actually deleted, then use memory_list to check what remains.
Review the deletion before approving it. Deletion removes one matching record.
scope: project selects the deepest matching project record; that may be in an
ancestor directory. scope: global selects the global store. With no scope,
delete searches from the deepest project toward global. It does not erase every
copy of that identifier. A shallower copy can become visible after deletion.
Understand scope and precedence
Sonny reads global records, then owner-trusted project directories from the
outermost ancestor toward the working directory. A .sonny directory owned by
you or root participates; a directory owned by another user is skipped. For
retrieval and listing, the deepest readable, unexpired record wins an identifier
collision.
| Location | Example authority directory | Precedence for the same identifier |
|---|---|---|
| Global | <Sonny data directory>/memory/ | Lowest; visible across projects using this user store. |
| Parent project | study/.sonny/memory/ | Overrides global for work under that project. |
| Deeper project | study/review/.sonny/memory/ | Overrides the parent when working in study/review. |
A default project write targets the deepest existing .sonny directory on the
chain. If none exists, the first write creates one in the working directory.
Starting in a child directory alone does not create a child scope: an existing
parent .sonny remains the write target. Choose global scope explicitly when a
rule should apply across projects. Use memory_stats and returned scope labels
to inspect the actual chain. Scope labels in results are descriptions, not new
values to paste into the tool’s scope parameter.
Search has a release limitation
Search ranks matching records across scopes and returns snippets. On 0.12.7, a
matching global record can appear even when a deeper record with the same
identifier exists but does not match the query. For example, a search for a word
present only in the global version returned that global copy, while
memory_retrieve returned the overriding child version.
Check the search hit’s scope, then retrieve the identifier to inspect the effective record before relying on it. Do not assume search results and direct retrieval always select the same version. This limitation is retained in qualification; the successful retrieval/list precedence tests do not establish stronger search semantics.
What reaches a new model request
The qualified TUI startup path injects a compact digest headed
Project memory (sonny). Each digest block lists up to 100 records by
identifier and title, with category and global/project scope labels. A trailer
reports additional records. Bodies are retrieved separately. The installed cap
check seeded 101 records and observed 100 unique identifiers per block, an
additional-record notice and no body text.
Suppress this startup digest for a new TUI process with:
SONNY_NO_MEMORY_INJECT=1 sonny run --new --file ./approved-recipes/review-work.yaml --no-trace
The flag does not delete the YAML records or remove the tools. Qualification
retrieved a real record in that disabled-digest TUI session after Allow once.
It does not retract a digest already present in an existing conversation. The
qualified headless --prompt journeys did not receive this startup digest;
check the actual client path rather than relying on ambient recall.
Search cache, backup and privacy
The authoritative records live in project .sonny/memory/ directories and the
global data directory’s memory/. Use sonny config path for your active standard
directories. Search derives SQLite FTS databases under the cache directory’s
memindex/, keyed by scope path. The index contains record text; it is not an
encrypted vault. A search refreshes an index when its record file set changes.
If a cache needs rebuilding, close concurrent users of it, identify the resolved
cache memindex/ directory, and remove only that derived cache. The next search
recreates it from YAML. Removing the data directory’s memory/ instead would
delete authoritative records. Qualification removed existing FTS databases,
observed recreation and verified that every YAML hash was unchanged.
For a backup, stop memory writers and copy the intended YAML directories, preserving file ownership and restrictive permissions. Include the global store and each relevant project/ancestor scope, and record where each belongs. Restore into the intended scope while writers are stopped; review collisions before overwriting existing identifiers. A tested file restore brought back the original record and its timestamps, and a new tool call retrieved it. Restoring old records can revive outdated decisions. The search index can be rebuilt; a session database or credential bundle is not needed to restore these YAML records.
Deletion is not secure erasure. A derived index can retain a removed record’s body
until refreshed or removed. Stored results, conversation exports, provider requests,
traces and backups can contain copies. The qualified post-delete search removed
the record from results and refreshed the index; that did not erase older output
files. --no-trace does not prevent provider transmission or make those other
copies disappear.
Project scope does not automatically commit files or synchronize them to another machine. If you place records in Git or a shared directory, consider who can read that copy. Global scope deliberately broadens reuse within this user store; it is not a shared Lanning knowledge store.
If a record is missing
Check the working directory and memory_stats, then list and retrieve at the
expected scope. A directory can be outside the owner-trusted chain; a record can
be expired, malformed, oversized or unreadable. Listing skips malformed records
and filename/identifier mismatches. Preserve the YAML for inspection before
changing it. Rebuilding a search cache cannot recreate a missing authority file.
Continue with sessions and continuation , plan review and approval or the exact memory settings reference .