Memory#
gptme can carry context across conversations in several complementary ways: some you curate yourself, some the agent writes, and some are retrieved automatically based on what you are working on.
System |
What it is and when it is loaded |
Read more |
|---|---|---|
Lessons and skills |
Curated Markdown guidance. Lessons are included automatically when their
keywords, patterns, or tools match; skills are loaded by name or invoked
as slash commands ( |
|
Persistent memory ( |
Memory entries plus |
|
|
Lets the agent save a memory itself during a conversation. Enabled by default. |
|
|
CLI to list, save, audit, and recall memory entries. Usable from any harness, including Claude Code and Codex. |
|
|
Deprecated. Problem/resolution pairs stored as JSONL, superseded by
|
|
RAG |
Indexes and searches documentation, code, and past conversations with
the |
|
Ambient retrieval |
The |
|
|
A project command whose output is added to the system prompt. It can
read the first prompt from |
Memory, lessons, or skills?#
These systems overlap, but each has a distinct job:
Lessons are curated guidance that shapes how the agent works. They are written and reviewed ahead of time, usually versioned in a repository, and included automatically when their
match:keywords, patterns, or tools come up in a conversation.Skills are named workflows, loaded when a task calls for them or invoked directly as slash commands.
Memory holds facts learned while working: user preferences, decisions and their rationale, and project facts that are not obvious from the code. Agents and harnesses write it at runtime; the index is always loaded, and full entries are found with
recall.
Rule of thumb: if it tells the agent how to behave, write a lesson; if it is a procedure to run on request, write a skill; if it records what is true about a user or project, save a memory.
Migrating from the knowledge base#
gptme-util knowledge stored problem/resolution pairs in JSONL. It is
deprecated: the subgroup prints a DeprecationWarning and will be removed in
a future release. Memory entries cover the same job, with typed entries and
recall that every harness can read.
Legacy JSONL entries are no longer injected into sessions by default. Existing
knowledge save/list/search/delete commands and the stored JSONL remain
available until retirement.
For compatibility, explicitly include knowledge_inject in your hook
allowlist:
$ HOOK_ALLOWLIST=knowledge_inject,cli_confirm gptme
HOOK_ALLOWLIST replaces the default hook set, including mode-specific
confirmation hooks. The interactive example includes cli_confirm to retain
tool confirmation; add other hooks you need to the comma-separated list.
Applications can include knowledge_inject in an explicit
init_hooks(allowlist=[...]) call, alongside their confirmation hook
(cli_confirm for interactive CLI, or server_confirm and
server_elicit for server mode). New memories should use the shared Markdown
memory toolkit below.
To move existing entries over:
$ gptme-util memory migrate-knowledge-jsonl
After migrating, use gptme-util memory recall QUERY in place of knowledge
search, and gptme-util memory save NAME DESCRIPTION in place of
knowledge save.
Lessons and memory are separate stores, and neither reads the other:
Lessons are read recursively from lesson directories (
./lessons/,./.gptme/lessons/,~/.config/gptme/lessons/,~/.agents/lessons/, and[lessons] dirsin config) and usematch:frontmatter.Memory entries are read from the top level of each memory root (
./memory/, the Claude Code project memory directory,$GPTME_AGENT_WORKSPACE/memory/, and~/.config/gptme/memory/, or onlyGPTME_MEMORY_DIRSwhen it is set) and use Claude Code-style frontmatter. An entry’s optionalkeywordsfield only affectsrecallranking.
Keep them in separate directories. There is no tooling that converts between them; if a memory turns out to be lasting guidance, rewrite it as a lesson and remove the memory entry.
Cross-harness memory#
gptme-util memory provides one local, Markdown-based memory store that can
be shared by gptme, Claude Code, Codex, and other agent harnesses. It is
modeled on Claude Code’s auto-memory: entries use Claude Code-compatible
frontmatter and are read from layered roots (project, Claude Code, agent, and
user); the nearest entry wins when names collide.
Inspect and write memory#
$ gptme-util memory roots
$ gptme-util memory list
$ gptme-util memory show prefer-short-answers
$ gptme-util memory save prefer-short-answers \
"User prefers short, direct answers." --type feedback < details.md
$ gptme-util memory index
index prints by default. Pass --write only when you want to replace the
selected root’s MEMORY.md with a generated index.
Persistent index selection and budget#
A large memory root can keep a small, curated always-on view without removing
living entries from recall. To opt in, create .memory-index.json inside
that memory directory:
{
"version": 1,
"budget": 21000,
"selected": ["prefer-short-answers.md", "review-policy.md"]
}
selected contains unique local filenames, not entry names. Every selected
file must exist and be living. The generated view retains type/name grouping;
the selection list is not a display ordering. Entries omitted from this list
remain available to recall, list, and show. An empty list produces
only the index header.
index (print, --write, and --check), save, and supersede all
use the persistent UTF-8 byte budget, including headers and newlines. Required
entries are never silently dropped: an oversized view fails before changing
entries, policy, or index. --budget can tighten the stored cap for one
invocation, but cannot raise it. Edit the policy to change the persistent cap.
In a managed root, save regenerates the selected view. New entries stay
unselected until explicitly added to the policy. Updating an entry preserves
its lifecycle, provenance, and omitted title/type/metadata; supplied description
and body replace the previous values. Updating an existing entry requires valid
frontmatter, including in a legacy root. Lenient parsing remains available for
reads, but writes refuse malformed YAML instead of silently discarding fields
while attempting a repair. Correct that file’s frontmatter before saving again.
supersede transfers a selected old
filename to its replacement, deduplicating the list, and commits both entries,
the policy, and the view together. Ordinary write failures roll back these
replacements; this is not a crash-recovery transaction log.
Before migrating a hand-curated index, copy its link text into entry title
and its instructions into description. Validate a copy with index and
review both link coverage and wording before index --write. Preserve any
archive as history. Direct file writers and harness hooks must adopt the same
policy; creating the policy does not intercept tools that edit files themselves.
When editing policy by hand, pause concurrent memory writers for that root.
Roots without a policy retain legacy behavior: save upserts one pointer
line, and explicit index generation includes living entries with an optional
one-shot truncating budget.
Supersession and audit#
Replace an obsolete entry with an already-existing living entry, then check the root’s strict YAML and bidirectional supersession links:
$ gptme-util memory supersede old-belief new-belief
$ gptme-util memory audit
$ gptme-util memory audit --quiet
supersede updates both entries (superseded_by on the old entry and
supersedes on the replacement) and regenerates the selected root’s index.
Both entries must be in the same root; use --scope when needed. The command
refuses malformed YAML and invalid field types rather than rewriting them
through the lenient read fallback. audit exits non-zero for malformed
entries, duplicate names, dangling targets, or asymmetric links, making
audit --quiet suitable for lifecycle hooks.
Recall#
Recall searches all living entries across the layered roots:
$ gptme-util memory recall "How should private code be reviewed?" -k 3
$ gptme-util memory recall "exact identifier" --format json
With gptme-rag and its lexical extra installed, --backend auto uses
its TF-IDF index. A minimal gptme installation falls back to a stdlib
token-overlap scorer. Output always reports the backend that actually ran; use
--backend tfidf when fallback would be unacceptable.
Text search#
search is a lightweight alternative to recall when you want exact
substring matching rather than relevance ranking. It filters living entries
whose name, description, or body contains the given pattern
(case-insensitive):
$ gptme-util memory search "deploy"
$ gptme-util memory search "auth" --type project
$ gptme-util memory search "review" --json
Use recall when you have a natural-language query and want the best few
hits; use search when you know a specific word or phrase and want all
matching entries.
Export#
export renders the store in a format suited to a specific harness:
$ gptme-util memory export --view cc > path/to/MEMORY.md
$ gptme-util memory export --view codex >> AGENTS.md
--view cc produces the same MEMORY.md index that index generates,
suitable for piping directly into Claude Code’s auto-loaded file or into a
hook payload.
--view codex produces a ## Memory section listing living entries with
the standard recall instruction, ready to paste into an AGENTS.md file
when bootstrapping a new Codex workspace. Both views respect --scope and
--budget.
Triggered matching#
Use keyword triggers when a memory must arrive on this turn, not only in the
session-start index. MEMORY.md is a budgeted always-on view; triggered
matching is for the smaller set of facts that should interrupt the current
action (a release checklist, a private-review rule, a recurring failure mode).
When to save a trigger phrase. Attach --keyword as you write the memory,
using the phrase you will actually see in a prompt or tool call. Prefer short,
distinctive phrases and lesson-style wildcards (deploy*). Names,
descriptions, and bodies never trigger, so a memory named release-rule will
stay silent until you give it keywords. Skip keywords on ambient facts that
already belong in the index.
$ gptme-util memory save release-rule "Verify releases before declaring done" --keyword "stable release" --keyword "deploy*" < rule.md
$ gptme-util memory match "Cut the stable release" --format json
Repeated --keyword options replace the entry’s phrases; omit the option to
keep them. From Python, keywords=[] clears them. Saving still follows the
index policy and budget; matching is read-only.
How hooks give you the memory in time. Put memory match on
PreToolUse so a pending tool call can still pull the matching rule before
it runs. Keep memory recall on UserPromptSubmit for broader search at
the start of a turn (see the hook configuration below). --prompt - reads the
Claude Code JSON event from stdin: the prompt for UserPromptSubmit, every
string under tool_input for PreToolUse (nested dicts and lists
included). Outer envelope fields (session_id, transcript_path,
cwd, tool_name) never match; nested keys inside tool_input that
reuse those names are tool arguments and do. --format hook-json returns
empty additionalContext on no match so the hook stays silent.
Dedup and budget belong to the wrapper. The CLI does not remember what it
already injected. Call gptme.memory.match.match_memories from the harness
and apply your own session dedup and injection budget to the returned entries,
scores, and matched_by reasons. -k (default 5) and --body-chars
(default 1200 Unicode code points) are hard caps, not a substitute for that.
Matching uses the same layered-root precedence as the rest of the store:
superseded and historical entries stay excluded, a nearer entry of any status
occupies the name, and index selection does not restrict who can trigger.
Keywords are case-insensitive substrings; * matches word characters, never
spaces or punctuation. Empty keywords and a lone * are disabled. Each
matching keyword scores one point. Equal scores keep name order.
Claude Code hook#
Add the command below to a UserPromptSubmit hook in
.claude/settings.json. It reads Claude Code’s JSON event from stdin and
returns a valid additionalContext response:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "gptme-util memory recall --prompt - --format hook-json",
"timeout": 20
}
]
}
],
"PreToolUse": [
{
"hooks": [
{
"type": "command",
"command": "gptme-util memory match --prompt - --format hook-json",
"timeout": 20
}
]
}
]
}
}
The hook is read-only and returns empty additionalContext when there is no
relevant memory. Keep the executable on Claude Code’s hook PATH; if the
workspace uses an isolated environment, call its absolute gptme-util path.
Codex / AGENTS.md integration#
Codex has no hook mechanism, so memory access is driven by instructions in
AGENTS.md. The gptme repository’s own AGENTS.md already contains a
## Memory section; copy or adapt it for any workspace that runs Codex
sessions.
The two key patterns for Codex agents:
Recall at session start — surface memories relevant to the current task:
gptme-util memory recall "<one-line task description>" -k 5
Save a memory — persist something worth keeping across sessions:
gptme-util memory save <slug> "<one-line description>" --type <type> <<'EOF'
<body>
EOF
Valid --type values match Claude Code’s memory taxonomy: user,
feedback, project, reference.
The entry is written to project memory/ when that directory exists,
otherwise to ~/.claude/projects/<workspace-hash>/memory/. The selector
checks existence, not writability: an unwritable project directory makes
save fail rather than falling back.
gptme-util memory recall (and the Claude Code hook) search every
layered root, so other harnesses see the entry on their next recall.
gptme’s session-start workspace prompt also loads the indexes of every
existing root, within a shared 64 KB budget. Claude Code’s native
MEMORY.md auto-load reads only the Claude Code root, so pass
--scope cc when the memory must appear in Claude Code without running
recall.
Writer provenance#
Harness wrappers can preserve their session identity through the shared writer:
gptme-util memory save decision "Reviewed decision" --type reference \
--metadata '{"originSessionId":"session-123","node_type":"memory"}' < body.md
--metadata accepts a JSON object and merges its keys into existing entry
metadata. Omitted keys survive updates; lifecycle fields and root index policy
remain governed by the store. The reserved type key must use --type
instead. Invalid JSON, a non-object, or a reserved key fails before writing.