---
description: "KnowledgeMemory for storing and retrieving structured knowledge entries"
tags:
  - memory
  - knowledge
---

# Knowledge Module

**Import:** `from selectools.knowledge import KnowledgeMemory`
**Stability:** beta

```python title="knowledge_quick.py"
import tempfile
from selectools.knowledge import KnowledgeMemory

# KnowledgeMemory stores long-term facts on disk (no API key needed)
with tempfile.TemporaryDirectory() as tmpdir:
    km = KnowledgeMemory(storage_dir=tmpdir, max_log_days=30)

    # Store persistent and transient knowledge
    km.remember("Preferred language: Python", category="preferences", persistent=True)
    km.remember("Working on Project Atlas today", category="context", persistent=False)

    # Build context for system prompt injection
    context = km.build_context()
    print(context)
    # [Long-term Memory]
    # ## preferences
    # - Preferred language: Python
    #
    # [Recent Memory]
    # - [...] (context) Working on Project Atlas today

    # Persistent facts survive log pruning
    facts = km.get_persistent_facts()
    print(f"Persistent categories: {list(facts.keys())}")
```

!!! tip "See Also"
    - [Knowledge Graph](KNOWLEDGE_GRAPH.md) - Relationship tracking between entities
    - [Entity Memory](ENTITY_MEMORY.md) - LLM-powered entity extraction and tracking

---

**Added in:** v0.16.0 (enhanced in v0.17.4)
**File:** `src/selectools/knowledge.py`, `knowledge_sanitizers.py`, `knowledge_store_redis.py`, `knowledge_store_supabase.py`
**Classes:** `KnowledgeMemory`, `KnowledgeEntry`, `KnowledgeStore`, `FileKnowledgeStore`, `SQLiteKnowledgeStore`, `RedisKnowledgeStore`, `SupabaseKnowledgeStore`

!!! tip "v0.17.4 Enhancements"
    Knowledge Memory now supports pluggable store backends (File, SQLite, Redis, Supabase),
    importance scoring (0.0–1.0), TTL per entry, category filtering, and importance-based eviction.
    The original file-based API is fully backward compatible.

## Table of Contents

1. [Overview](#overview)
2. [Quick Start](#quick-start)
3. [Architecture](#architecture)
4. [KnowledgeMemory Class](#knowledgememory-class)
5. [The remember() Method](#the-remember-method)
6. [Context Building](#context-building)
7. [Auto-Registered Tools](#auto-registered-tools)
8. [Agent Integration](#agent-integration)
9. [Log Pruning](#log-pruning)
10. [Backends for Ephemeral Infrastructure](#backends-for-ephemeral-infrastructure)
11. [Pre-Save Sanitization](#pre-save-sanitization)
12. [Best Practices](#best-practices)

---

## Overview

The **Knowledge** module provides cross-session, long-term memory for selectools agents. Unlike [Entity Memory](ENTITY_MEMORY.md) (which tracks entities within a conversation) or [Knowledge Graph](KNOWLEDGE_GRAPH.md) (which tracks relationships), Knowledge Memory is a simple, durable store where agents (and users) can explicitly save and recall facts, preferences, and instructions that persist indefinitely.

### Purpose

- **Long-Term Memory**: Facts that survive across sessions, restarts, and deployments
- **Daily Logs**: Time-stamped memory entries for recent context
- **Persistent MEMORY.md**: A durable file of important facts flagged as persistent
- **Auto-Registered Tools**: The agent can call `remember()` to save knowledge and `recall()` to search it during conversations
- **Category Organization**: Memories are tagged with categories for structured recall

### When to Use Each Memory Type

| Memory Type | Scope | Lifetime | Use Case |
|---|---|---|---|
| `ConversationMemory` | Single session | Until cleared | Multi-turn dialogue context |
| `EntityMemory` | Entities mentioned | Session / persisted | "Who is Alice?" |
| `KnowledgeGraphMemory` | Relationships | Session / persisted | "How are X and Y related?" |
| **`KnowledgeMemory`** | **Explicit facts** | **Indefinite** | **"Remember that I prefer dark mode"** |

---

## Quick Start

```python
from selectools import Agent, AgentConfig, OpenAIProvider, ConversationMemory, Message, Role
from selectools.knowledge import KnowledgeMemory

knowledge = KnowledgeMemory(storage_dir="./agent_memory")

agent = Agent(
    tools=[],
    provider=OpenAIProvider(),
    memory=ConversationMemory(max_messages=50),
    config=AgentConfig(knowledge_memory=knowledge),
)

# The agent can now use the auto-registered "remember" tool
result = agent.run([
    Message(role=Role.USER, content="Remember that my preferred language is Python and I work at Acme Corp.")
])

# Later (even after restart):
knowledge2 = KnowledgeMemory(storage_dir="./agent_memory")
agent2 = Agent(
    tools=[],
    provider=OpenAIProvider(),
    memory=ConversationMemory(),
    config=AgentConfig(knowledge_memory=knowledge2),
)

result = agent2.run([
    Message(role=Role.USER, content="What programming language do I prefer?")
])
# Agent knows: Python (loaded from persistent memory)
```

---

## Architecture

KnowledgeMemory uses a two-tier storage model:

```
./agent_memory/
+-- MEMORY.md              # persistent facts (survives pruning)
+-- logs/
    +-- 2026-03-13.jsonl   # daily log entries
    +-- 2026-03-12.jsonl
    +-- 2026-03-11.jsonl
    +-- ...
```

### MEMORY.md (Long-Term)

A Markdown file containing facts explicitly flagged as persistent. These are always loaded and never pruned.

```markdown
# Agent Memory

## preferences
- Preferred language: Python
- Dark mode: enabled

## personal
- Works at Acme Corp
- Name: Alice

## technical
- Uses PostgreSQL for production databases
- Prefers pytest over unittest
```

### Daily Logs (Recent)

JSONL files containing timestamped memory entries. These provide recent context and are subject to pruning.

```json
{"timestamp": "2026-03-13T10:15:00Z", "category": "preferences", "content": "Preferred language: Python", "persistent": true}
{"timestamp": "2026-03-13T10:16:00Z", "category": "context", "content": "Working on Project Atlas this week", "persistent": false}
{"timestamp": "2026-03-13T14:30:00Z", "category": "technical", "content": "Uses PostgreSQL for production databases", "persistent": true}
```

---

## KnowledgeMemory Class

### Constructor

```python
class KnowledgeMemory:
    def __init__(
        self,
        storage_dir: str = "./agent_memory",
        max_log_days: int = 30,
        max_context_entries: int = 50,
    ):
        """
        Args:
            storage_dir: Directory for MEMORY.md and daily logs.
            max_log_days: Days to retain daily log files before pruning.
            max_context_entries: Max recent entries to include in context.
        """
```

### Core Methods

```python
def remember(
    self,
    content: str,
    category: str = "general",
    persistent: bool = False,
) -> str:
    """Store a knowledge entry.

    Args:
        content: The fact or information to remember.
        category: Organizational category (e.g., "preferences", "personal", "technical").
        persistent: If True, also write to MEMORY.md for indefinite retention.

    Returns:
        Confirmation message.
    """

def build_context(self) -> str:
    """Build context string for system prompt injection.

    Combines persistent facts from MEMORY.md with recent daily log entries.
    Returns a formatted block with [Long-term Memory] and [Recent Memory] sections.
    """

def get_persistent_facts(self) -> Dict[str, List[str]]:
    """Return all persistent facts, organized by category."""

def get_recent_entries(self, days: int = 7, limit: int = 50) -> List[Dict[str, Any]]:
    """Return recent log entries from the last N days."""

def prune_logs(self, max_days: Optional[int] = None) -> int:
    """Delete daily log files older than max_days.

    Returns the number of log files deleted.
    """

def clear(self) -> None:
    """Remove all knowledge (MEMORY.md and all logs)."""

def clear_logs(self) -> None:
    """Remove daily logs only, preserving MEMORY.md."""
```

---

## The remember() Method

`remember()` is the primary interface for storing knowledge:

```python
knowledge = KnowledgeMemory(storage_dir="./agent_memory")

# Transient memory (daily log only)
knowledge.remember(
    content="Currently debugging a timeout issue in the API",
    category="context",
    persistent=False,
)

# Persistent memory (daily log + MEMORY.md)
knowledge.remember(
    content="Preferred editor: VS Code",
    category="preferences",
    persistent=True,
)
```

### Behavior

| `persistent` | Daily Log | MEMORY.md | Survives Pruning |
|---|---|---|---|
| `False` | Written | Not written | No (deleted after `max_log_days`) |
| `True` | Written | Appended | Yes (MEMORY.md is never pruned) |

### Categories

Categories organize memories in MEMORY.md under Markdown headers:

```python
knowledge.remember("Name: Alice", category="personal", persistent=True)
knowledge.remember("Prefers Python", category="preferences", persistent=True)
knowledge.remember("Uses VS Code", category="preferences", persistent=True)
```

Produces in MEMORY.md:

```markdown
# Agent Memory

## personal
- Name: Alice

## preferences
- Prefers Python
- Uses VS Code
```

---

## Context Building

`build_context()` assembles a prompt-ready context block from both storage tiers:

```python
context = knowledge.build_context()
```

Output:

```
[Long-term Memory]
## preferences
- Preferred language: Python
- Dark mode: enabled

## personal
- Works at Acme Corp
- Name: Alice

[Recent Memory]
- [2026-03-13 10:16] (context) Working on Project Atlas this week
- [2026-03-13 14:30] (technical) Investigating timeout in payment service
- [2026-03-13 15:00] (context) Meeting with Bob about Atlas milestone
```

### Section Details

| Section | Source | Content |
|---|---|---|
| `[Long-term Memory]` | `MEMORY.md` | All persistent facts, organized by category |
| `[Recent Memory]` | Daily log files | Last N entries (up to `max_context_entries`) |

If either section is empty, it is omitted from the output.

---

## Auto-Registered Tools

When `knowledge_memory` is set in `AgentConfig`, a `remember` tool and a `recall` tool are automatically registered on the agent. This allows the LLM to save and search knowledge during conversations without any additional configuration. If you supply your own tool named `remember` or `recall`, the auto-registered version is skipped for that name only.

### Tool Definition

```python
@tool(description="Save important information to long-term memory for future reference")
def remember(content: str, category: str = "general", persistent: bool = False) -> str:
    """Remember a fact or piece of information.

    Args:
        content: The information to remember.
        category: Category for organization (e.g., "preferences", "personal", "technical").
        persistent: Whether this should be stored permanently.

    Returns:
        Confirmation message.
    """
    return knowledge_memory.remember(content, category, persistent)
```

### Usage in Conversation

```
User: "Remember that I prefer dark mode and my timezone is PST."

Agent calls: remember(
    content="User prefers dark mode",
    category="preferences",
    persistent=True,
)

Agent calls: remember(
    content="User timezone is PST",
    category="preferences",
    persistent=True,
)

Agent: "I've saved your preferences. I'll remember that you prefer dark mode
and your timezone is PST."
```

The agent decides when to call `remember()` based on the conversation context. Explicit requests like "remember that..." and "save this..." reliably trigger the tool.

### The recall Tool

The `recall` tool is the retrieval counterpart of `remember`. It keyword-searches stored entries (case-insensitive match against content and category) and returns the closest matches ranked by relevance, then importance:

```python
recall(query="dark mode", limit="3")
# Found 1 memory:
# 1. [preferences] User prefers dark mode
```

- `query` (required): keywords describing the information to look up. An empty query returns the most important entries.
- `limit` (optional): maximum number of memories to return. Defaults to `'5'`; invalid or non-positive values fall back to the default.

If nothing matches, the tool returns `No memories found matching '<query>'.` so the agent knows to ask the user instead of guessing.

### remember + recall Together

```
User: "Remember that I prefer dark mode."
Agent calls: remember(content="User prefers dark mode", category="preferences", persistent="true")

--- new session, days later ---

User: "Set up the editor the way I like it."
Agent calls: recall(query="editor preferences dark mode")
Tool returns: "Found 1 memory:\n1. [preferences] User prefers dark mode"
Agent: "Done — dark mode enabled, as you prefer."
```

Both tools can also be created manually via `selectools.toolbox.memory_tools.make_remember_tool(km)` and `make_recall_tool(km)` for agents that need only one of them or a custom tool list.

---

## Agent Integration

### Configuration

```python
from selectools import Agent, AgentConfig, OpenAIProvider, ConversationMemory
from selectools.knowledge import KnowledgeMemory

knowledge = KnowledgeMemory(
    storage_dir="./agent_memory",
    max_log_days=30,
    max_context_entries=50,
)

agent = Agent(
    tools=[...],  # your tools -- "remember" is added automatically
    provider=OpenAIProvider(),
    memory=ConversationMemory(max_messages=50),
    config=AgentConfig(knowledge_memory=knowledge),
)
```

### Integration Flow

```
Agent.__init__()
    |
    +-- Register "remember" tool automatically
    |
    +-- knowledge_memory.build_context()
    |   +-- Load MEMORY.md and recent logs
    |   +-- Build [Long-term Memory] + [Recent Memory] block
    |
    +-- Inject context into system prompt

run() / arun() called
    |
    +-- System prompt includes knowledge context
    |
    +-- Execute agent loop
    |   +-- LLM may call remember() tool
    |   +-- Tool writes to daily log (+ MEMORY.md if persistent)
    |
    +-- Return AgentResult
```

### Combining with Session Storage

```python
from selectools.sessions import SQLiteSessionStore
from selectools.knowledge import KnowledgeMemory

agent = Agent(
    tools=[...],
    provider=OpenAIProvider(),
    memory=ConversationMemory(),
    config=AgentConfig(
        knowledge_memory=KnowledgeMemory(storage_dir="./memory"),
        session_store=SQLiteSessionStore(db_path="sessions.db"),
        session_id="user-42",
    ),
)
# Session storage handles conversation memory.
# Knowledge memory handles long-term facts (separate storage).
```

### Combining with Entity and Knowledge Graph Memory

```python
agent = Agent(
    tools=[...],
    provider=OpenAIProvider(),
    memory=ConversationMemory(),
    config=AgentConfig(
        entity_memory=EntityMemory(max_entities=100, provider=OpenAIProvider()),
        knowledge_graph=KnowledgeGraphMemory(
            store=SQLiteTripleStore(db_path="kg.db"),
            provider=OpenAIProvider(),
        ),
        knowledge_memory=KnowledgeMemory(storage_dir="./memory"),
    ),
)
# System prompt includes:
# [Known Entities] -- from entity memory
# [Known Relationships] -- from knowledge graph
# [Long-term Memory] -- from knowledge memory
# [Recent Memory] -- from knowledge memory
```

---

## Log Pruning

Daily log files older than `max_log_days` are pruned automatically or on demand:

```python
knowledge = KnowledgeMemory(
    storage_dir="./agent_memory",
    max_log_days=30,  # auto-prune logs older than 30 days
)

# Manual pruning
deleted = knowledge.prune_logs()
print(f"Pruned {deleted} old log files")

# Override max_days for a one-time cleanup
deleted = knowledge.prune_logs(max_days=7)
```

### Pruning Behavior

- Only daily log files (`.jsonl`) are deleted
- `MEMORY.md` is never pruned (persistent facts are permanent)
- Pruning runs at the start of `build_context()` if stale logs exist
- Returns the count of deleted files

### Storage Growth

```
Typical daily log: ~1-10 KB per day
30 days retention: ~30-300 KB total
MEMORY.md: ~1-50 KB (depends on usage)
```

---

## Backends for Ephemeral Infrastructure

`KnowledgeMemory` is filesystem-based, but Railway, Fly.io, Lambda, Cloud Run,
and Vercel Functions wipe `/tmp` between deploys. The `KnowledgeBackend`
protocol (beta) upstreams the standard workaround: **scratch on disk during
the request, persist to blob/DB between requests.**

```python
from selectools import KnowledgeMemory
from selectools.knowledge_backends import SupabaseKnowledgeBackend

memory = KnowledgeMemory(
    directory="/tmp/agent-memory",
    backend=SupabaseKnowledgeBackend(client, key="user-123"),
)
```

On construction the directory is restored from the backend; after every
mutation (`remember()`, `prune_old_logs()`) the directory is packed into a
single blob and saved back. `backend=None` (the default) keeps the original
filesystem-only behavior exactly. Call `memory.flush()` after mutating
`memory.store` directly or hand-writing files into the directory.

### The Protocol

Two methods, single-blob contract:

```python
class KnowledgeBackend(Protocol):
    def load_bytes(self) -> Optional[bytes]: ...
    def save_bytes(self, data: bytes) -> None: ...
```

Any object with these two methods works — write your own adapter in ~10 lines.

### SupabaseKnowledgeBackend

Postgres table via Supabase PostgREST, mirroring `SupabaseSessionStore`
client handling (you create the client, pass it in; `supabase` is a lazy
optional dependency: `pip install selectools[supabase]`).

```python
from supabase import create_client
from selectools.knowledge_backends import SupabaseKnowledgeBackend

client = create_client(SUPABASE_URL, SERVICE_ROLE_KEY)
backend = SupabaseKnowledgeBackend(client, key="user-123")
```

**Required table DDL** (run once in your Supabase project):

```sql
create table if not exists public.selectools_knowledge (
    key        text        primary key,
    data       text        not null,
    updated_at timestamptz not null default now()
);
alter table public.selectools_knowledge enable row level security;
```

The blob is stored as readable text in the `data` column (UTF-8 JSON archive;
non-UTF-8 payloads fall back to `b64:`-prefixed base64). To reuse an existing
table, override the layout **and switch to `write_mode="update"`**:

```python
SupabaseKnowledgeBackend(
    client,
    key=user_id,
    table_name="users",
    key_column="user_id",
    data_column="memory_text",
    write_mode="update",   # row must pre-exist (e.g. created by an auth trigger)
)
```

> **Warning — NOT NULL columns break the default upsert.** The default
> `write_mode="upsert"` issues `INSERT ... ON CONFLICT DO UPDATE` with a
> partial `{key, data, updated_at}` payload. Postgres validates NOT NULL
> constraints on the *proposed insert tuple* **before** conflict arbitration,
> so any NOT-NULL-no-default column outside that payload (e.g. `users.email`)
> raises `not_null_violation` (error 23502) on every save — even when the row
> already exists. `write_mode="update"` issues a plain
> `UPDATE ... WHERE key_column = key` instead (Sheriff runs this update-by-key
> pattern in production, sheriff#302) and raises a clear `RuntimeError` when
> the row doesn't exist yet. Keep the default `"upsert"` for the dedicated
> `selectools_knowledge` table above.

### RedisKnowledgeBackend

Mirrors the `RedisSessionStore` pattern: lazy `import redis`
(`pip install selectools[cache]`), prefix namespace, optional server-side TTL.

```python
from selectools.knowledge_backends import RedisKnowledgeBackend

backend = RedisKnowledgeBackend(
    key="user-123",
    url="redis://localhost:6379/0",
    prefix="selectools:knowledge:",   # default
    ttl=86400,                         # optional, seconds
)
```

### Notes

- The backend snapshots the **directory**. A custom `store` whose data lives
  outside the directory (e.g. `SQLiteKnowledgeStore` with a db path elsewhere)
  is not covered by the snapshot.
- Construct one `KnowledgeMemory` per request/user on ephemeral infra so the
  restore-on-init / save-on-mutation cycle brackets each request.
- `KnowledgeBackend` differs from `KnowledgeStore`: the store handles
  structured entries and querying; the backend persists the whole directory
  (store files, daily logs, `MEMORY.md`) as one opaque blob.

---

## Pre-Save Sanitization

Remembered content is user-derived and flows back into the system prompt via
`build_context()` — which makes the knowledge store a **prompt-injection
vector**. The `pre_save` hook (beta) lets you sanitize or reject entry text
before anything is persisted:

```python
from selectools import KnowledgeMemory
from selectools.knowledge_sanitizers import defang_delimiters, strip_surrogates

memory = KnowledgeMemory(
    directory="./memory",
    pre_save=[strip_surrogates, defang_delimiters],  # applied in order
    dedupe=True,                                      # reject near-duplicates
)
```

### Hook Semantics

A hook is any `Callable[[str], Optional[str]]`:

- **Return transformed text** — persistence proceeds with the new text
  (store entry, daily log, and `MEMORY.md` all receive the sanitized form).
- **Return `None`** — the entry is rejected: nothing is written anywhere,
  `remember()` returns an empty string, and a debug-level log records which
  hook rejected it.

`pre_save` accepts a single callable or a sequence applied in order; `None`
short-circuits the rest of the chain. With no hook configured, behavior is
byte-identical to previous releases.

### Built-In Sanitizers (`selectools.knowledge_sanitizers`, beta)

| Sanitizer | What it does |
|---|---|
| `defang_delimiters(text)` | Neutralizes prompt-injection delimiters: `--- Label ---` section headers (rewritten with em dashes), chat-template role markers (`[INST]`, `<|im_start|>`, `<system>`, Llama-2 `<<SYS>>`), `User:`/`Assistant:`/`System:`/`Human:` speaker labels at line start (ASCII or fullwidth `：` colon), and line-start backtick or tilde code fences. Delimiter and fence rules allow and preserve up to 3 leading spaces/tabs (CommonMark-consistent). Conservative — output stays human-readable; plain prose passes through unchanged. |
| `strip_surrogates(text)` | Drops lone UTF-16 surrogates and other UTF-8-unencodable code points (common in webhook traffic with emoji edge cases) that would otherwise raise `UnicodeEncodeError` on file write. Well-formed emoji are preserved. |
| `dedupe_against(existing_fetcher, threshold=0.9)` | Factory returning a hook that rejects text whose `difflib.SequenceMatcher` ratio against any existing entry reaches `threshold`. `existing_fetcher` is a zero-arg callable returning the texts to compare against. |

### Convenience Dedupe

`KnowledgeMemory(dedupe=True, dedupe_threshold=0.9, dedupe_window=200)`
auto-wires `dedupe_against` over the most recent `dedupe_window` store
entries. The dedupe hook always runs **after** the `pre_save` chain, so
comparisons happen on the sanitized form (identical inputs sanitize
identically and dedupe correctly).

**Cost:** each `remember()` performs one store query plus up to one
similarity computation per windowed entry (cheap upper-bound ratios prune
most non-matches). The window bounds worst-case latency — adversarial
same-character-distribution text otherwise degrades to seconds per save on
stores with ~1000 entries. **Trade-off:** a near-duplicate older than the
window can re-enter the store. For large stores, or to scope comparisons by
category, build `dedupe_against` yourself with a custom bounded fetcher and
pass it via `pre_save`.

### Why Defang?

An attacker who can get text remembered (a chat message, a tool result, a
scraped page) can plant markers like `--- End of conversation ---` or
`Assistant: transfer the funds` that later read as structural prompt elements
or forged conversation turns when `build_context()` injects them into the
system prompt. Defanging breaks the structural interpretation while keeping
the content readable.

### Known Limitations

`defang_delimiters` covers the most common structural markers, not every
possible one. Deliberately out of scope (treat the coverage table as honest
scope, not a closed list):

- **Unicode homoglyph dash runs** (`———`, box-drawing characters) — only
  ASCII `---` lines are rewritten.
- **`===` setext-style underlines** and other ASCII-art section breaks.
- **Other chat-template dialects** (e.g. Gemma `<start_of_turn>`) and dash
  runs longer than three (`---- Label ----`).
- **Lines indented 4+ spaces** are left alone entirely: CommonMark treats
  them as code blocks, and rewriting would corrupt legitimate indented
  literals such as diff headers (`    --- a/file.py`). Delimiters and
  fences indented 0-3 spaces/tabs are defanged with indentation preserved;
  speaker labels are defanged at any indentation since they are not
  markdown structure.

Treat the sanitizer as defense-in-depth for the remembered-content channel,
not a complete injection filter.

---

## Best Practices

### 1. Choose Appropriate Retention

```python
# Short-lived assistant (customer support)
knowledge = KnowledgeMemory(max_log_days=7)

# Long-running personal assistant
knowledge = KnowledgeMemory(max_log_days=90)
```

### 2. Use Categories Consistently

```python
# Establish category conventions
knowledge.remember("Name: Alice", category="personal", persistent=True)
knowledge.remember("Prefers dark mode", category="preferences", persistent=True)
knowledge.remember("Uses PostgreSQL", category="technical", persistent=True)
knowledge.remember("Meeting at 3pm", category="context", persistent=False)
```

### 3. Flag Important Facts as Persistent

```python
# Transient -- will be pruned
knowledge.remember("Working on bug #1234 today", category="context")

# Persistent -- survives indefinitely
knowledge.remember("API key rotation policy: every 90 days", category="technical", persistent=True)
```

### 4. Inspect Stored Knowledge

```python
# View persistent facts
facts = knowledge.get_persistent_facts()
for category, entries in facts.items():
    print(f"\n{category}:")
    for entry in entries:
        print(f"  - {entry}")

# View recent entries
recent = knowledge.get_recent_entries(days=3, limit=20)
for entry in recent:
    print(f"[{entry['timestamp']}] ({entry['category']}) {entry['content']}")
```

### 5. Separate Storage Per User

```python
def create_agent_for_user(user_id: str) -> Agent:
    return Agent(
        tools=[...],
        provider=OpenAIProvider(),
        memory=ConversationMemory(),
        config=AgentConfig(
            knowledge_memory=KnowledgeMemory(
                storage_dir=f"./memory/{user_id}",
            ),
        ),
    )
```

---

## Testing

```python
import tempfile
import os

def test_remember_and_recall():
    with tempfile.TemporaryDirectory() as tmpdir:
        km = KnowledgeMemory(storage_dir=tmpdir)

        km.remember("Prefers Python", category="preferences", persistent=True)
        km.remember("Meeting at 3pm", category="context", persistent=False)

        context = km.build_context()
        assert "[Long-term Memory]" in context
        assert "Prefers Python" in context
        assert "[Recent Memory]" in context
        assert "Meeting at 3pm" in context


def test_persistent_facts_survive_clear_logs():
    with tempfile.TemporaryDirectory() as tmpdir:
        km = KnowledgeMemory(storage_dir=tmpdir)

        km.remember("Important fact", category="general", persistent=True)
        km.remember("Transient note", category="context", persistent=False)

        km.clear_logs()

        facts = km.get_persistent_facts()
        assert "Important fact" in facts.get("general", [])

        recent = km.get_recent_entries()
        assert len(recent) == 0


def test_memory_md_categories():
    with tempfile.TemporaryDirectory() as tmpdir:
        km = KnowledgeMemory(storage_dir=tmpdir)

        km.remember("Name: Alice", category="personal", persistent=True)
        km.remember("Likes Python", category="preferences", persistent=True)
        km.remember("Uses VS Code", category="preferences", persistent=True)

        facts = km.get_persistent_facts()
        assert len(facts["personal"]) == 1
        assert len(facts["preferences"]) == 2


def test_log_pruning():
    with tempfile.TemporaryDirectory() as tmpdir:
        km = KnowledgeMemory(storage_dir=tmpdir, max_log_days=0)

        km.remember("Old note", category="context")

        deleted = km.prune_logs()
        assert deleted >= 0  # may be 0 if same-day


def test_remember_tool_registration():
    with tempfile.TemporaryDirectory() as tmpdir:
        km = KnowledgeMemory(storage_dir=tmpdir)

        agent = Agent(
            tools=[],
            provider=LocalProvider(),
            memory=ConversationMemory(),
            config=AgentConfig(knowledge_memory=km),
        )

        tool_names = [t.name for t in agent.tools]
        assert "remember" in tool_names
```

---

## API Reference

| Class | Description |
|---|---|
| `KnowledgeMemory(storage_dir, max_log_days, max_context_entries)` | Cross-session knowledge store with daily logs and persistent MEMORY.md |

| Method | Returns | Description |
|---|---|---|
| `remember(content, category, persistent)` | `str` | Store a knowledge entry |
| `build_context()` | `str` | Build `[Long-term Memory]` + `[Recent Memory]` context |
| `get_persistent_facts()` | `Dict[str, List[str]]` | All MEMORY.md facts by category |
| `get_recent_entries(days, limit)` | `List[Dict]` | Recent daily log entries |
| `prune_logs(max_days)` | `int` | Delete old log files, return count |
| `clear()` | `None` | Remove all knowledge |
| `clear_logs()` | `None` | Remove logs only, keep MEMORY.md |

| AgentConfig Field | Type | Description |
|---|---|---|
| `knowledge_memory` | `Optional[KnowledgeMemory]` | Knowledge memory instance; auto-registers `remember` tool |

---

## Further Reading

- [Memory Module](MEMORY.md) - Conversation memory (in-session)
- [Entity Memory Module](ENTITY_MEMORY.md) - Entity attribute tracking
- [Knowledge Graph Module](KNOWLEDGE_GRAPH.md) - Relationship tracking
- [Sessions Module](SESSIONS.md) - Session persistence for conversation state
- [Agent Module](AGENT.md) - How agents use knowledge context

---

**Next Steps:** See how all memory types work together in the [Architecture doc](../ARCHITECTURE.md).

---

## Related Examples

| # | Script | Description |
|---|--------|-------------|
| 111 | [`111_knowledge_sanitizers.py`](https://github.com/johnnichev/selectools/blob/main/examples/111_knowledge_sanitizers.py) | Pre-save sanitization: defang, surrogate stripping, dedupe |
| 37 | [`37_knowledge_memory.py`](https://github.com/johnnichev/selectools/blob/main/examples/37_knowledge_memory.py) | Long-term knowledge memory with daily logs |
| 20 | [`20_customer_support_bot.py`](https://github.com/johnnichev/selectools/blob/main/examples/20_customer_support_bot.py) | Production bot with knowledge persistence |
| 36 | [`36_knowledge_graph.py`](https://github.com/johnnichev/selectools/blob/main/examples/36_knowledge_graph.py) | Knowledge graph (relationship tracking) |
| 35 | [`35_entity_memory.py`](https://github.com/johnnichev/selectools/blob/main/examples/35_entity_memory.py) | Entity memory (attribute tracking) |
