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.
Memory is not facts-only: the Claude Code format includes a feedback type
for guidance a harness learns from the user (“stage explicit paths”, “don’t
steal window focus”). Give such an entry keywords and it is injected when a
keyword appears: gptme’s lesson auto-inclusion matches keyworded memories
alongside lessons, and other harnesses get the same behaviour by running
memory match (for example from a Claude Code hook; see
Triggered matching). Prefer a lesson when the rule is curated and
shared across workspaces; a keyworded feedback entry suits rules learned at
runtime that live with a harness’s memory directory.
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 with separate formats, and neither loader reads the other’s directories:
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 optionalkeywordsmake it a triggered rule formemory match(see Triggered matching);recallalso uses them for ranking.
Keep them in separate directories. A memory entry with keywords covers the
same ground as a lesson with match.keywords, in the memory format, so either
can hold a behavioural rule. gptme’s lesson auto-inclusion matches both, with
one shared session budget and dedup; set GPTME_LESSONS_INCLUDE_MEMORIES=false
to match lessons only. Memory candidates match on keywords alone (no
match.tools or match.patterns). There is no automatic converter: to
move a lesson into memory, copy its body into an entry (typically
type: feedback) and turn match.keywords into keywords; to promote a
memory into a curated lesson, do the reverse. Remove the original either way,
so the rule has one home.
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.
Choosing scope in the memory tool#
The conversational memory tool uses the same root selector as the CLI.
Choose scope="user" for preferences or feedback that should follow you
across projects; choose project for local facts or cc for the current
workspace’s Claude Code memory directory. The native tool accepts name,
content, and optional scope. In Markdown format:
```memory review-in-parallel --scope user
Check review feedback concurrently with CI rather than blocking on CI alone.
This preference applies across projects.
```
XML format carries the same shell-quoted arguments in its args attribute:
<tool-use>
<memory args="review-in-parallel --scope user">
Check review feedback concurrently with CI rather than blocking on CI alone.
This preference applies across projects.
</memory>
</tool-use>
In XML, --scope is reserved for scope selection. To save a literal name
containing that token, quote the entire name inside the attribute, for example
args="'deployment --scope user'". Other unscoped XML names retain their
whole-attribute behavior; serialized literal names with --scope are quoted.
Available scopes are project, cc, agent, user, and explicit.
project requires an existing workspace memory/ directory; agent
requires an existing configured agent memory directory. user writes under
<gptme config directory>/memory/. When GPTME_MEMORY_DIRS is set, it
replaces all other roots: only explicit is available. An unavailable or
invalid scope fails without falling back or creating another root.
Relative paths in GPTME_MEMORY_DIRS and GPTME_AGENT_WORKSPACE are resolved
against the selected workspace (the conversation workspace on the server),
before directory availability checks and duplicate-root removal. Absolute paths
and ~-expanded paths keep their configured destinations.
Without a scope, the tool follows the CLI default: first explicit root, else
existing project memory/, else the workspace’s CC root. This is not a
user-wide default, and no text classifier chooses a scope for you. Confirmation
and success show the actual selected scope and destination. Interactive tool
confirmation hooks receive destination metadata separately from editable memory
text; editing the memory does not add that metadata to the saved entry. The
selected root is made absolute before confirmation and retained throughout
confirmation and saving, even if the process directory changes. The shared writer updates the
index under its existing policy and budget.
Temporary Git worktrees retain their own project/CC identity under the existing
workspace resolver. A project fact stays local to that worktree unless you
explicitly configure a shared root; use user for cross-project feedback or
a configured agent root for agent-wide knowledge. This tool does not remap
a worktree to the primary checkout, migrate memories, or delete old entries.
Saving is not the same as startup delivery: gptme loads the layered indexes
within its shared prompt budget, not every full entry. Claude Code’s native
auto-load covers its project CC index, not an arbitrary user root; configure
the recall hook below to search the other layers. Codex requires explicit
recall instructions or an exported index in AGENTS.md as described below.
A policy-managed entry may remain recall-only until selected for the 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, preserving unrelated hand-curated index text, and explicit index generation
includes living entries with an optional one-shot truncating budget. Legacy
save stages the entry and index before replacing either and rolls back an
already-published entry if index publication fails. This handles ordinary write
errors, not process crashes or uncooperative external writers. Existing file
permission bits, POSIX ownership and Linux POSIX access ACLs are preserved;
when an existing file has no access ACL, inherited staging ACLs are removed.
Ownership or ACL preservation errors abort before publication. Other
platform-specific ACLs are not covered by this
guarantee. Atomic replacement is authorized by the
parent directory: a read-only index file in a writable directory can still be
replaced, just as in a managed root. Legacy save and direct pointer updates
follow a symlinked MEMORY.md and atomically replace its target, leaving the
link intact. The target’s parent directory authorizes that replacement. Both
legacy writers hold a stable .<target-name>.memory.lock sidecar beside the
resolved index target from reading through publication and rollback, so roots
sharing that target serialize their updates. Writers that do not use this
sidecar remain uncoordinated.
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
distinctive multi-word phrases and lesson-style wildcards (deploy*).
Keywords match as substrings, so short single words fire inside unrelated
words: ci matches “decide”, pr matches “prompt”, gate matches
“navigate”. 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. By default the CLI does not remember what it already injected.
Pass --dedup-session to deliver each entry at most once per session: the
session_id comes from the hook payload (or --session-id for plain text),
and delivered entry paths are kept in the gptme state directory, keyed by a hash
of the id and pruned after a week. Wrappers that need a different policy can
call gptme.memory.match.match_memories and filter_unseen themselves and
apply their own 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 --dedup-session",
"timeout": 20
}
]
}
]
}
}
The hook is read-only and returns empty additionalContext when there is no
relevant memory. PreToolUse fires on every tool call, so keep
--dedup-session on it; without the flag an entry whose keyword keeps
matching is re-injected each time. 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.