agent-memory-git 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Memories</title>
7
+ <link rel="stylesheet" href="/app.css">
8
+ </head>
9
+ <body>
10
+ <header class="top">
11
+ <div class="bar">
12
+ <div class="search">
13
+ <svg class="search-ico" viewBox="0 0 16 16" aria-hidden="true"><circle cx="7" cy="7" r="4.8"/><path d="m10.6 10.6 3.4 3.4"/></svg>
14
+ <input id="q" type="search" placeholder="Search memories" autocomplete="off" spellcheck="false" aria-label="Search memories">
15
+ <kbd>/</kbd>
16
+ </div>
17
+ <span class="sync" id="sync" hidden></span>
18
+ <button class="btn primary" id="new" title="New memory (n)">New</button>
19
+ </div>
20
+ <div class="filters" id="filters"></div>
21
+ </header>
22
+
23
+ <main class="list" id="list" aria-label="Memories"></main>
24
+ <div class="selbar" id="selbar" hidden></div>
25
+
26
+ <aside class="drawer" id="drawer" aria-hidden="true"></aside>
27
+ <div class="toasts" id="toasts" aria-live="polite"></div>
28
+ <div class="boot" id="boot">Loading memories…</div>
29
+ <script src="/app.js"></script>
30
+ </body>
31
+ </html>
@@ -0,0 +1,135 @@
1
+ """Finding the real project folder behind a throwaway session folder.
2
+
3
+ Claude Code sessions started without a folder run in a scratch workspace, and
4
+ the Codex desktop app creates a fresh folder per thread. A `here` memory
5
+ recorded there would never match a later session, so we look at the session
6
+ transcript and attribute the memory to the folder the session worked in most.
7
+ """
8
+
9
+ import json
10
+ import os
11
+ import re
12
+ from pathlib import Path
13
+
14
+ from . import config
15
+ from .util import HOME, is_under
16
+
17
+ DEFAULT_THROWAWAY = [
18
+ r"/scratch-workspaces/", # Claude Code, no folder chosen
19
+ r"^~/Documents/Codex/\d{4}-\d{2}-\d{2}/[^/]+", # Codex desktop, one folder per thread
20
+ ]
21
+ # Folders that only hold other folders; never the answer on their own.
22
+ CONTAINERS = re.compile(r"^(Library/CloudStorage(/[^/]+)?|Library/Application Support|"
23
+ r"Library/Mobile Documents|Documents|Documents/Codex|"
24
+ r"Documents/Codex/\d{4}-\d{2}-\d{2}|Desktop|Downloads)$")
25
+ TAIL_BYTES = 8 * 1024 * 1024 # new memories are about recent work
26
+ MIN_HITS = 3
27
+ DESCEND_SHARE = 0.6
28
+ MAX_DEPTH = 4 # levels below home; a git root always ends the descent
29
+
30
+
31
+ def _patterns(home):
32
+ pats = config.load().get("throwaway_patterns") or DEFAULT_THROWAWAY
33
+ return [re.compile(p.replace("^~", "^" + re.escape(str(home)), 1)) for p in pats]
34
+
35
+
36
+ def is_throwaway(path, home=None):
37
+ return bool(path) and any(p.search(path) for p in _patterns(home or HOME))
38
+
39
+
40
+ # Under ~/Library only synced document folders can hold projects.
41
+ LIBRARY_PROJECT_ROOTS = ("CloudStorage", "Mobile Documents")
42
+
43
+
44
+ def _evidence(line):
45
+ """True for transcript lines that show what the session did.
46
+
47
+ Instructions and injected context (AGENTS.md, CLAUDE.md, memory summaries,
48
+ hook output) mention many unrelated folders, so only tool calls, tool
49
+ results and the conversation itself count.
50
+ """
51
+ try:
52
+ d = json.loads(line)
53
+ except ValueError:
54
+ return False
55
+ kind = d.get("type")
56
+ if kind == "response_item": # Codex rollout
57
+ payload = d.get("payload") or {}
58
+ return payload.get("type") != "message" or payload.get("role") == "assistant"
59
+ return kind in ("user", "assistant") # Claude Code transcript
60
+
61
+
62
+ def _read_evidence(path):
63
+ try:
64
+ size = os.path.getsize(path)
65
+ with open(path, "rb") as f:
66
+ if size > TAIL_BYTES:
67
+ f.seek(size - TAIL_BYTES)
68
+ raw = f.read().decode("utf-8", "replace")
69
+ except (OSError, TypeError):
70
+ return ""
71
+ return "\n".join(line for line in raw.splitlines() if _evidence(line))
72
+
73
+
74
+ def infer(transcript, cwd=None, exclude=(), home=None):
75
+ """The project folder a transcript refers to most, or None.
76
+
77
+ Counts every folder prefix (two to five levels below home) of the absolute
78
+ paths in the transcript, starts from the most referenced top-level folder,
79
+ and descends while one child holds most of the references, stopping at a
80
+ git checkout root.
81
+ """
82
+ home = Path(home or HOME)
83
+ text = _read_evidence(transcript) if transcript else ""
84
+ if not text:
85
+ return None
86
+ path_re = re.compile(re.escape(str(home)) + r"(?:/[^\s\"'\\`<>|;:,()\[\]{}*?]+)+")
87
+ counts = {}
88
+ for m in path_re.finditer(text):
89
+ parts = Path(m.group(0).rstrip(".")).relative_to(home).parts
90
+ if len(parts) < 2 or parts[0].startswith("."):
91
+ continue
92
+ if parts[0] == "Library" and parts[1] not in LIBRARY_PROJECT_ROOTS:
93
+ continue
94
+ for depth in range(2, min(len(parts), 5) + 1):
95
+ key = parts[:depth]
96
+ counts[key] = counts.get(key, 0) + 1
97
+
98
+ blocked = [str(Path(e)) for e in exclude if e]
99
+
100
+ def usable(key):
101
+ p = str(home.joinpath(*key))
102
+ if not os.path.isdir(p) or is_throwaway(p, home):
103
+ return False
104
+ if cwd and (p == cwd or is_under(cwd, p)):
105
+ return False
106
+ return not any(p == b or is_under(p, b) for b in blocked)
107
+
108
+ def descend(n, best):
109
+ limit = MAX_DEPTH + (2 if best[0] == "Library" else 0) # ~/Library/CloudStorage/<drive>/...
110
+ while len(best) < limit and not home.joinpath(*best, ".git").exists():
111
+ kids = [(c, k) for k, c in counts.items()
112
+ if len(k) == len(best) + 1 and k[:len(best)] == best and usable(k)]
113
+ if not kids:
114
+ break
115
+ c, k = max(kids)
116
+ if c < DESCEND_SHARE * n and not CONTAINERS.match("/".join(best)):
117
+ break
118
+ n, best = c, k
119
+ return None if CONTAINERS.match("/".join(best)) else best
120
+
121
+ # Most referenced first; a container that leads nowhere (e.g. ~/Documents/Codex, whose
122
+ # children are all throwaway threads) gives way to the next candidate.
123
+ top = sorted(((n, k) for k, n in counts.items() if len(k) == 2 and usable(k)), reverse=True)
124
+ for n, key in top:
125
+ if n < MIN_HITS:
126
+ break
127
+ best = descend(n, key)
128
+ if best:
129
+ return str(home.joinpath(*best))
130
+ return None
131
+
132
+
133
+ def default_exclude():
134
+ data = config.data_dir()
135
+ return [str(config.CODE_DIR)] + ([str(data)] if data else [])
@@ -0,0 +1,183 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-memory-git
3
+ Version: 0.1.0
4
+ Summary: Shared, git-backed memory for Claude Code and Codex.
5
+ Keywords: claude-code,codex,memory,agents,git
6
+ Author: Shichao Song
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Environment :: Console
10
+ Classifier: Operating System :: MacOS
11
+ Classifier: Operating System :: POSIX :: Linux
12
+ Classifier: Programming Language :: Python :: 3
13
+ Requires-Dist: numpy>=2.0 ; extra == 'ui'
14
+ Requires-Python: >=3.11
15
+ Project-URL: Homepage, https://github.com/Ki-Seki/agent-memory
16
+ Project-URL: Repository, https://github.com/Ki-Seki/agent-memory
17
+ Project-URL: Demo, https://github.com/Ki-Seki/agent-memory-data-demo
18
+ Provides-Extra: ui
19
+ Description-Content-Type: text/markdown
20
+
21
+ # agent-memory
22
+
23
+ One memory for Claude Code and Codex, kept as plain Markdown files in a git repository you own.
24
+
25
+ When a session starts, a hook gives the agent an index of the memories that matter where it is
26
+ running: everything marked global, plus what was learned in the same folder or repository. When
27
+ a turn ends, a hook commits what the agent wrote, with full provenance (tool, session, host,
28
+ folder, git remote), and pushes it. Both tools read and write the same files, so a preference
29
+ you gave Codex is there the next time Claude Code starts, on any machine that pulls the data
30
+ repository. This repository holds only the code; the memories live in a separate data
31
+ repository.
32
+
33
+ ![mem ui listing memories, with one open on the right](docs/mem-ui.png)
34
+
35
+ <sub>`mem ui` on the [demo data repository](https://github.com/Ki-Seki/agent-memory-data-demo).</sub>
36
+
37
+ ## Try it without installing
38
+
39
+ ```bash
40
+ git clone https://github.com/Ki-Seki/agent-memory
41
+ git clone https://github.com/Ki-Seki/agent-memory-data-demo
42
+ cd agent-memory
43
+ AGENT_MEMORY_DATA=../agent-memory-data-demo bin/mem ui --keywords-only
44
+ ```
45
+
46
+ `AGENT_MEMORY_DATA` points `mem` at the demo repository, so nothing is installed and no hooks
47
+ change. The page needs `numpy`. `--keywords-only` skips the optional embedding model; leave it
48
+ out after `mem ui --fetch-model` to also search by meaning.
49
+
50
+ ## How it works
51
+
52
+ - **One file per memory.** Frontmatter carries `type` (user / feedback / project / reference),
53
+ `scope` (global / here), a one-line `description`, and provenance under `source`: tool, session
54
+ id and name, model, host, folder, git root, remote, branch and commit. Files live in
55
+ `memories/YYYY/MM/<id>.md`; the folders exist only to keep listings short.
56
+ - **Session start.** A SessionStart hook works out where the session runs (folder, host, git
57
+ identity), picks every `global` memory plus the `here` memories recorded at the same place,
58
+ and injects a compact index (under 10,000 characters, the Claude Code hook limit).
59
+ - **Writing.** The agent writes new memories into a per-session staging folder and edits existing
60
+ files in place. At the end of each turn a Stop hook stamps ids and provenance, moves staged
61
+ files into `memories/`, commits, and pushes in the background. The agent never writes
62
+ provenance itself.
63
+ - **Rules.** [`PROTOCOL.md`](src/agent_memory/PROTOCOL.md) tells the agent what to save and how.
64
+ `mem install` copies it to `~/.config/agent-memory/PROTOCOL.md`, which `~/.claude/CLAUDE.md`
65
+ imports, and into `~/.codex/AGENTS.md` for Codex.
66
+
67
+ Sessions in throwaway folders (Claude Code scratch workspaces, Codex per-thread folders under
68
+ `~/Documents/Codex/<date>/`) get their `here` memories filed under the project folder the
69
+ transcript shows them working in most, with the original folder kept as `source.original_cwd`.
70
+ `mem relocate` applies the same inference to existing memories. Patterns can be overridden with
71
+ `throwaway_patterns` in `~/.config/agent-memory/config.json`.
72
+
73
+ A `here` memory matches a session when they share the folder (100), the git checkout (90), the
74
+ remote (80, other clones and worktrees), or when one folder contains the other (70 / 60). Home
75
+ and its direct children such as `~/repos` are too broad to match through containment.
76
+
77
+ ## Setup
78
+
79
+ Requires Python 3.11+, git and [uv](https://docs.astral.sh/uv/).
80
+
81
+ ```bash
82
+ uv tool install "agent-memory-git[ui]" # the mem command; [ui] adds numpy for mem ui
83
+ mem init ~/repos/agent-memory-data # create the data repository and ~/.config/agent-memory
84
+ mem import claude --dry-run # preview, then run without --dry-run
85
+ mem import codex --dry-run
86
+ mem install --dry-run # shows every change as a diff
87
+ mem install
88
+ ```
89
+
90
+ To upgrade, run `uv tool upgrade agent-memory-git` and then `mem install`, which refreshes the
91
+ protocol copies; `mem doctor` says when they are out of date.
92
+
93
+ `install` edits, and records in `~/.config/agent-memory/install-state.json`:
94
+
95
+ | File | Change |
96
+ |---|---|
97
+ | `~/.claude/settings.json` | SessionStart / Stop / SessionEnd hooks, `autoMemoryEnabled: false`, read/edit permission on the data repository, `mem search/list/show` allowed |
98
+ | `~/.claude/CLAUDE.md` | a marked block importing `~/.config/agent-memory/PROTOCOL.md`, a copy of the protocol |
99
+ | `~/.codex/hooks.json` | the same three hooks |
100
+ | `~/.codex/config.toml` | `memories.generate_memories = false`, `memories.use_memories = false`, data repository added to `sandbox_workspace_write.writable_roots` |
101
+ | `~/.codex/AGENTS.md` | a marked block with the protocol |
102
+ | `~/.local/bin/mem` | symlink to `mem`, only if that file does not exist yet (`uv tool install` already puts one there) |
103
+
104
+ Codex asks you to trust new hooks once: open `/hooks` in Codex and enable the three
105
+ agent-memory hooks.
106
+
107
+ `mem uninstall` reverts exactly those edits (other changes the tools made in the meantime are
108
+ kept) and turns native memory back on. The data repository is never touched by uninstall.
109
+
110
+ ## Commands
111
+
112
+ ```
113
+ mem index [--cwd DIR] what a session started here would see
114
+ mem list [--global|--here|--all] [--type T]
115
+ mem search WORDS... [--all] full-text search; --all includes other locations
116
+ mem show ID|NAME|PATH
117
+ mem add --name N --type T --scope S --description D BODY|-
118
+ mem flush commit anything left in staging
119
+ mem relocate [PATH...] [--dry-run] re-file memories recorded in throwaway folders
120
+ mem sync [--pull-only] pull --rebase and push the data repository
121
+ mem lint [ID...] [--summary] list memories that do not follow the body format
122
+ mem ui [--port N] [--no-open] search, filter, read and edit memories in a local page
123
+ mem doctor check the installation
124
+ ```
125
+
126
+ ## Managing memories in a page
127
+
128
+ `mem ui` starts a small server on `127.0.0.1` and opens a page listing every memory. Keep the
129
+ terminal open while you use it. It needs `numpy`; the hooks do not.
130
+
131
+ - **Find.** One search box (press `/`). Word matches appear as you type, with the matching words
132
+ marked; a moment later the local model adds memories close in meaning, across Chinese and
133
+ English. Filters narrow the list by type, where a memory loads (everywhere, or one project or
134
+ folder), which tool wrote it, keyword, age, and whether it needs tidying (what `mem lint`
135
+ reports, also listed on the open memory); the order is newest, oldest, recently changed or A to Z.
136
+ - **Read.** Click a row, or move with `j`/`k` and press Enter. The memory opens on the right with
137
+ every frontmatter field (including each `source` entry), the body rendered as Markdown with
138
+ `[[links]]` that open other memories, the file exactly as stored, and the most similar memories.
139
+ - **Change.** Edit (`e`) or delete from the open memory, add one with New (`n`), or tick several
140
+ rows (`x`) and delete them together. Every change is one git commit, pushed in the background,
141
+ with Undo. If an agent edits the same file meanwhile, saving is refused rather than overwriting.
142
+
143
+ Similar memories and search by meaning use Qwen3-Embedding-0.6B, int8 ONNX, on the CPU, in a
144
+ separate `uv run --with onnxruntime --with tokenizers` environment; vectors are cached by content
145
+ in `<data>/.cache/embeddings/`. Get the model once with `mem ui --fetch-model` (about 625 MB,
146
+ pinned revision, checked by SHA-256). Without it, or with `--keywords-only`, similar memories
147
+ come from keyword vectors and search matches words only.
148
+
149
+ Keywords are the words that most set a memory apart from the others (TF-IDF). Words that should
150
+ never be keywords, such as your own name, go in `keyword_stopwords` in
151
+ `~/.config/agent-memory/config.json`.
152
+
153
+ The server only listens on loopback, and every request needs the one-time token from the link it
154
+ prints, so other web pages cannot read or change memories.
155
+
156
+ ## Development
157
+
158
+ ```bash
159
+ uv sync --all-extras # dev tools and numpy
160
+ uv run pytest
161
+ uv run ruff check .
162
+ ```
163
+
164
+ From a checkout, `bin/mem` (or `uv run mem`) runs the code in place; `mem install` from there
165
+ points the hooks at that checkout, so edits take effect in the next session.
166
+ `uv tool install --editable .` does the same with `mem` on PATH.
167
+
168
+ To release, run `uv run scripts/release.py prepare patch` (or `minor`, `major`) to open a
169
+ version PR. After it is merged, `uv run scripts/release.py publish <PR_NUMBER>` creates the GitHub
170
+ Release, and GitHub Actions publishes it to PyPI.
171
+
172
+ ## Notes
173
+
174
+ - Hooks never fail the host session; errors go to `<data>/.cache/agent-memory.log`.
175
+ - Git operations are serialized with a lock file, so parallel sessions are safe on one machine.
176
+ Across machines, unique file names keep merge conflicts rare; a failed rebase is aborted and
177
+ logged for you to resolve.
178
+ - Session names come from Claude Code's `custom-title` transcript entries and Codex's thread
179
+ database, when available.
180
+
181
+ ## License
182
+
183
+ [MIT](LICENSE)
@@ -0,0 +1,29 @@
1
+ agent_memory/PROTOCOL.md,sha256=hIcQuU7_5pnZsRCQe6qwbIAM8oelkEqJDlJbRBKrKAI,5561
2
+ agent_memory/__init__.py,sha256=olPmw2ADtt5kRXBMCbzecXyclqRAzVJj1xDp14-i1gw,59
3
+ agent_memory/__main__.py,sha256=_gX5kpLNLWRafVMqXZG1H759ZYvqzfFQWm06dydxGVQ,108
4
+ agent_memory/capture.py,sha256=4hxKgcjv0BAqaBh8NcWu3EyXTlsO-Mzp3o79k-V8eCo,7295
5
+ agent_memory/cli.py,sha256=rZZ-PhaNckZJwzH8Fs-amuGTSRAPT8_in1vGbVMyqkM,11622
6
+ agent_memory/config.py,sha256=VaffcaZ3Tv0q2DSXnoSSQFPg21U-LkFTdbjnVp2J95M,1744
7
+ agent_memory/context.py,sha256=7qN2J6y78he06o1qnDIHsCOryVTmtv8vAlnkIJm2lRM,2239
8
+ agent_memory/embed_worker.py,sha256=Dxt2r3RgQGz1qCBQXylTTPr12DpMUaHowPlsVLleH0s,3656
9
+ agent_memory/frontmatter.py,sha256=L3MV9liCEcZet_F2rfYiDjowslqas4Qdq49XNrGi_xw,4081
10
+ agent_memory/gitsync.py,sha256=7RCuNTTp8t5bZUOJTrCorEt-x50LwrzKDaOIrIoZVYs,4318
11
+ agent_memory/hooks.py,sha256=sAJVfBYTAeRV499GTAfwEJvxoqAioVcq0yPibRKORf4,3151
12
+ agent_memory/importers.py,sha256=OELhZWMpJbC0vSSaFW2nQsH0pnV9B9_PAEo3t9FkOVA,10030
13
+ agent_memory/install.py,sha256=z9FHVdH3ERTG7CcBL5bNVXtkkVBiuajkfwx9Xw1Zcpg,19353
14
+ agent_memory/lint.py,sha256=0sBNM1Si2vVx4C_gfiQyqBM84eFeFn396S4T01WwItI,1999
15
+ agent_memory/select.py,sha256=rLVrxeB71HEGFzETU0TeN9NsKqSTO0KMGHRq4V3qnm4,3636
16
+ agent_memory/sessions.py,sha256=GhKPn-JwjVWmunUp43-CyzT_UosSl-g8PgfOaIa7hDo,1906
17
+ agent_memory/store.py,sha256=u0K6OS11aQYcklFQMcRDzPvv2B1nzJfrg8Yb3j1geOw,3134
18
+ agent_memory/ui.py,sha256=XFQr7XwAOT8pSFKb20wwkuKCEtQzpW2gOBTBx3vsNII,19162
19
+ agent_memory/ui_server.py,sha256=Oo1Bu38ri67eFG8Qxn9TY_V1wjn0Ru8YBnx0ba0XamY,16676
20
+ agent_memory/util.py,sha256=AnrPFOkUuJc0pbx6l3R3YgzYmZCdehEGH-zTesjgHPI,2640
21
+ agent_memory/webui/app.css,sha256=MFy6pVxSiP2yuA8lYtvcP_mO73SV8jbMCsC-Pau6Nx4,13996
22
+ agent_memory/webui/app.js,sha256=Qv2Vcte9Rs4GOUxt97LYkcKnXu18rKrHBQNkjqD49zg,34863
23
+ agent_memory/webui/index.html,sha256=LlOPxJ7_c0-mBmfGGi03H1dwPCFQEqk3DCG4GwX1GoU,1092
24
+ agent_memory/workdir.py,sha256=rEhDu_8PceFtBNPsnxouKg6u7h4QH_EzMJJ53dqeckE,5175
25
+ agent_memory_git-0.1.0.dist-info/licenses/LICENSE,sha256=paJRjXM3OSoEnxnAmj9GxNgOlnSYVoQKpRb9YsTeIN4,1069
26
+ agent_memory_git-0.1.0.dist-info/WHEEL,sha256=-i9oRNYVXXZJUIYl5zclLIg6onEb0NLibTX34uln84w,81
27
+ agent_memory_git-0.1.0.dist-info/entry_points.txt,sha256=26FMYMESWCr0b6i8W27V6emfz2sEPIYea5CXscSmQAQ,47
28
+ agent_memory_git-0.1.0.dist-info/METADATA,sha256=OmRRaLqttn3U3k0akSzHN1Rl4ouXxnTKxH1GGgU4dVs,9540
29
+ agent_memory_git-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.12.13
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ mem = agent_memory.cli:main
3
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shichao Song
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.