Tutorial — a memory that keeps separate contexts separate
The L0–L3 agent memory example
examples/agent_memory.py is a live terminal walkthrough of a four-layer agent
memory. It follows the L0–L3 design in
TencentDB Agent Memory:
conversations become small memories, memories become independently maintained scene
blocks, and changed scene blocks inform a durable persona.
It is an opt-in example catalog, published as agent_memory@0.3.0 when the script
starts. It does not change the default Memseek catalog.
What a scene is¶
A scene is one editable context document for one coherent area of work. It might be the billing API migration, the team’s UI design rules, an incident response method, or a decision that keeps affecting later work.
It is not a chat summary, a task-list row, or a container holding every scene. Each
scene has its own stable name and its own history. Updating the billing scene does
not replace the UI scene. This is why scenes can list several ▣ entries.
Every scene uses the same readable sections:
| Section | What it tells an agent |
|---|---|
| Work Context | what this context is about |
| Applies When | when the guidance is relevant |
| Core SOP | the repeatable steps to take |
| Decision Logic | why those steps and trade-offs apply |
| Prohibitions and Failure Modes | what not to do and why |
| Key Evidence | facts that support the guidance |
| Related Tasks and Assets | work still in flight and useful references |
| Evolution Record | meaningful changes or contradictions over time |
| Open Questions | uncertainty that still matters |
These headings keep the block natural for a person to read while making its categories reliable for a self-healing agent parser.
The four layers¶
| Layer | Stores | Use it for |
|---|---|---|
| L0 Conversation | the original messages | verify exact wording, time, and source |
| L1 Atom | individual facts, preferences, rules, and events | recall one actionable claim precisely |
| L2 Scene | one maintained context document per topic | restore a working context quickly |
| L3 Persona | stable cross-context traits and working patterns | adapt an agent’s behaviour consistently |
L0 messages what was said
│ l1_extract
L1 memories atomic facts and rules
│ scene_synthesis
L2 scenes separate Markdown context blocks
│ persona
L3 persona durable cross-context patterns
L0 and L1 preserve history. L2 and L3 keep a current version for each named block or trait while retaining the earlier version for audit. The persona runs after an L2 block changes, so it is based on consolidated context rather than a single raw message.
Run it¶
The terminal walkthrough and the web reactor use the same example, but they have different jobs:
examples/agent_memory.pywrites real records and waits for the real worker./showcase/agent-memory/simulates the cascade immediately and can also load a read-only snapshot of the real run through Memseek's HTTP API.
A real JSON-capable model is required for the full ladder. LLM_FAKE=1 can store
and display L0 messages, but cannot create cited L1, L2, or L3 records. The model
behind the strong alias must follow structured output and reproduce source UUIDs
exactly.
1. Install dependencies and start PostgreSQL¶
From the repository root:
uv sync --frozen --all-groups
export DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:55432/memseek_test
make database
uv run memseek migrate
Use the same DATABASE_URL in every terminal below. If you keep local settings in
an untracked .env.sh, source .env.sh in each terminal instead of repeating the
exports.
2. Configure the model used by the example¶
examples/agent_memory_catalog/conf/models.yaml declares the endpoint, model IDs,
and credential variable. Its shipped configuration reads OPENAI_API_KEY:
If you use another OpenAI-compatible provider, change the endpoint and model IDs
in that catalog file and export the credential variable named by api_key_env.
The API, worker, and example must all see the same provider configuration.
3. Start the API with the web origin allowed¶
In terminal A:
export DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:55432/memseek_test
export OPENAI_API_KEY='replace-with-your-provider-key'
export API_CORS_ORIGINS='["http://127.0.0.1:4321"]'
uv run uvicorn memseek.api:app --host 127.0.0.1 --port 8000
The CORS value is a JSON array of exact origins. http://localhost:4321 and
http://127.0.0.1:4321 are different origins; add the one shown by Astro. Do not
use a wildcard.
4. Start the worker¶
In terminal B:
export DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:55432/memseek_test
export OPENAI_API_KEY='replace-with-your-provider-key'
uv run memseek worker
Keep this process running. It owns l1_extract, scene_synthesis, and persona,
so the upper layers remain empty while the worker is stopped.
5. Create a reusable workspace and run the example¶
In terminal C, create a workspace before starting the example. The API key is printed once; the commands below capture it without echoing it:
export DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:55432/memseek_test
workspace_json="$(uv run memseek create-workspace agent-memory-web)"
export MEMSEEK_API_KEY="$(printf '%s' "$workspace_json" | \
uv run python -c 'import json,sys; print(json.load(sys.stdin)["api_key"])')"
export MEMSEEK_BASE_URL=http://127.0.0.1:8000
export AGENT_MEMORY_RUN_ID=demo
uv run python examples/agent_memory.py
AGENT_MEMORY_RUN_ID makes the entity name predictable: this run writes to
agent.alice-demo. It accepts 1–32 ASCII letters, digits, hyphens, or underscores.
Without it, the script generates a random six-character ID and prints the entity
name in the terminal.
The script publishes agent_memory@0.3.0, writes both sessions, waits for the
cascade, demonstrates recall and provenance, then opens the interactive prompt.
Keep it open if you want to try say, scenes, trace, and the other commands
below.
6. Start the interactive website¶
In terminal D:
Open http://127.0.0.1:4321/showcase/agent-memory/. The staged reactor, recall composer, provenance graph, and skill loop work without a backend. To attach the live snapshot, scroll to Connect API and enter:
| Field | Value |
|---|---|
| API URL | http://127.0.0.1:8000 |
| Workspace API key | the value in terminal C's MEMSEEK_API_KEY |
| Run ID | demo |
Click Connect to Memseek. The page checks /health, then requests
/timeline?entity=agent.alice-demo&status=all&limit=100 with the workspace bearer
key. It groups the returned rows into L0 messages, L1 memories, L2 scenes, and L3
persona. The key stays in the input in the current tab; the page does not write it
to local storage, cookies, or the URL.
For a remote deployment, serve both pages over HTTPS and add the website's exact
origin to API_CORS_ORIGINS, for example ["https://memory.example.com"]. Never
embed a workspace key in the JavaScript bundle. This connector is an
operator-facing development aid; give public users a server-side proxy or
short-lived, narrowly scoped credentials instead.
If you do not need the web connector, you may omit MEMSEEK_API_KEY and let the
script create a fresh disposable workspace from DATABASE_URL. The interactive
terminal still works, but the browser will not know that generated workspace key.
Use the interactive prompt¶
| Command | What it does |
|---|---|
say <text> |
add a message and watch the memory update |
scenes |
list the actual L2 scene documents |
memories |
show the atomic L1 memories |
rules [floor] |
show high-priority instructions |
persona |
show stable L3 traits |
recall <task> |
render the context an agent would receive |
l0 [session] |
replay the original messages |
trace <id> |
follow a record back to its evidence |
status |
show the number of records at each layer |
Open a new scene¶
Tell the agent about a distinct, durable context. Then run scenes.
say The payments migration is under way. We must finish it before September 10, but the new webhook contract is still unapproved.
scenes
You will see a block similar to this:
▣ Payments Migration
Work Context
The payments migration is under way and depends on approval of the new webhook contract.
Core SOP
- Keep the migration plan and contract approval together.
Open Questions
- When will the webhook contract be approved?
You do not create an empty scene with a separate command. The example extracts the durable facts and decides whether they belong in an existing block or warrant a new one. If a message really describes a separate context, name that separation plainly:
say This is separate from payments: the office move is blocked until the new lease is signed, and it must happen by September 20.
scenes
The list should now contain both Payments Migration and Office Move. A second
scene is a second block, not a second item hidden inside the first one.
Add a standard or rule¶
Standards can be scenes too. This makes them visible instead of leaving them buried in an unrelated project:
When no existing design block covers that rule, the example creates a separate
UI Design Guidelines scene. The original L1 instruction remains visible through
rules, and a genuinely cross-context pattern can later inform persona.
How a scene changes¶
The default action is update: new evidence about billing rewrites the billing block, preserving its useful context and adding the new information naturally.
A new block is created only for a distinct durable context. Related blocks may be merged when they describe the same method or narrative. The catalog holds at most 15 live blocks; near that limit it merges related context before opening another.
Finishing work does not make its scene disappear. The outcome, prohibitions, and lessons are still useful when an agent resumes work or investigates a later problem. A block is removed only as part of an explicit merge or when its context has truly become obsolete; its prior versions remain auditable.
If scenes is empty or always shows one block¶
First make sure the worker is running and you are using a real model credential. Then give the model enough context to distinguish the new work from the old work:
- Say what is separate: “This is separate from payments …”.
- Include a durable fact, constraint, decision, or method—not only a greeting or a temporary chat remark.
- Run
scenesafter thescene_synthesisline completes. Thestatuscommand should show the same count of scenario blocks.
If the run says that no scene block changed, the model judged the message to be
covered by existing context or too short-lived to preserve. That is a no-op, not a
hidden scene. With LLM_FAKE=1, no scene can be created at all.
What the walkthrough also demonstrates¶
The scripted tour shows every layer building from a real platform conversation, then repeats part of that conversation with a moved deadline and a new constraint. It also includes a separate reviewed-procedure example and a bounded recall prompt.
The catalog lives in examples/agent_memory_catalog/:
collections/ messages · memories · scenes · persona
derivations/ l1_extract · scene_synthesis · persona
views/ recall · standing instructions · memory audit
artifacts/ the bounded agent context prompt
The important L2 detail is simple: scenes contains one record per scene block.
Any index used to navigate those blocks is system metadata and is never presented as
the only scene to an end user.