agent-memory-cli 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,8 @@
1
+ # Known Debt
2
+
3
+ Use this file to track verified, unresolved project debt that future sessions
4
+ should not rediscover from scratch.
5
+
6
+ | Item | Severity | Date Found | Source | Status |
7
+ | --- | --- | --- | --- | --- |
8
+ | _None yet._ | | | | |
@@ -0,0 +1,3 @@
1
+ # Open Threads
2
+
3
+ - None yet.
@@ -0,0 +1,4 @@
1
+ # Project Facts
2
+
3
+ <!-- Stable facts about this project: what it is, where it runs, who owns what. -->
4
+ <!-- Rewrite in place as facts change; the history of a change belongs in decision_log.md. -->
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: agent-memory
3
+ description: Cross-session memory for this repository. Use at session start to read the bounded memory context (recall), and whenever a decision is made that a later session must not rediscover (capture). Also use when asked what was decided before.
4
+ ---
5
+
6
+ # agent-memory workflow for {runtime}
7
+
8
+ Managed by `agent-memory setup {runtime}`: a rerun regenerates this file only
9
+ while its digest matches a shipped template; an edited file is reported as a
10
+ conflict and kept. The rules are the same for every runtime; only the agent
11
+ name differs.
12
+
13
+ ## Recall, at session start
14
+
15
+ The session-start hook printed the startup manifest: the memory files to read,
16
+ in order, with their readability only. No content was injected. Before acting
17
+ on project work, read the bounded context in one step:
18
+
19
+ agent-memory recall
20
+
21
+ It prints the project's active files (`project_facts.md`, `decision_log.md`,
22
+ `open_threads.md`, `known_debt.md`), then the curated org files (`recent.md`,
23
+ `decisions.md`, `rules.md`), cut at a character budget (`--max-chars`, default
24
+ 8000). Cite a decision by its date heading and its text. `archive/`, `events/`
25
+ and `debriefs/` are never loaded; read them only when the work needs them.
26
+
27
+ ## Capture, when a decision is made
28
+
29
+ When the user decides something that a future session must not rediscover,
30
+ record it once, in one sentence, with the reason:
31
+
32
+ agent-memory capture --agent {runtime} "<the decision>" --why "<the reason>" --source "<PR, issue or message>"
33
+
34
+ It appends a dated entry, newest first, to the project's `decision_log.md`,
35
+ with provenance: the agent, the UTC time and the source. Capture only what the
36
+ user decided, not what you propose; confirm first when in doubt. One decision
37
+ per entry. Stable facts go to `project_facts.md` by hand, not through capture.
38
+
39
+ ## Out of scope
40
+
41
+ No synthesis, search or index; no writes to the org tier; no push. Syncing the
42
+ home is `agent-memory push`, run deliberately, never from this workflow.
@@ -0,0 +1,287 @@
1
+ # SPDX-FileCopyrightText: 2026 Kiloloop
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """The minimal memory workflow: ``agent-memory capture`` and ``agent-memory recall``.
4
+
5
+ Two verbs and one authored text. ``capture`` appends one decision, with its
6
+ provenance, to the project's ``decision_log.md``, newest first under a UTC
7
+ date heading; it never writes through a symlink (a linked component anywhere
8
+ below the home, the file included, is refused, never followed), replaces the
9
+ file atomically with the file's own mode, and touches no other file. ``recall`` prints the content of the files the
10
+ startup manifest lists, in the manifest's order, cut at a character budget
11
+ with a notice: the bounded read the manifest describes but never performs.
12
+ ``archive/``, ``events/`` and ``debriefs/`` are never loaded.
13
+
14
+ The authored text is the workflow file ``setup <runtime>`` installs beside the
15
+ hook, rendered from one shipped template with the runtime's name; every
16
+ runtime's wrapper says the same thing.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import datetime as dt
22
+ import os
23
+ import re
24
+ import secrets
25
+ import stat as stat_mod
26
+ from importlib import resources
27
+ from pathlib import Path
28
+ from typing import Any, Dict, List, Optional
29
+
30
+ from . import layout, startup
31
+
32
+ SCHEMA_VERSION = 1
33
+ DECISION_FILE = "decision_log.md"
34
+ DEFAULT_AGENT_ENV = "AGENT_MEMORY_AGENT"
35
+ WORKFLOW_TEMPLATE = "templates/workflow/SKILL.md"
36
+
37
+ _HEADING = re.compile(r"^## (\d{4}-\d{2}-\d{2})\s*$")
38
+ _SPACE = re.compile(r"\s+")
39
+
40
+
41
+ class WorkflowError(Exception):
42
+ """A precondition failed; nothing was written."""
43
+
44
+
45
+ # --- the authored text ---------------------------------------------------------
46
+
47
+
48
+ def workflow_text(runtime: str) -> str:
49
+ """The workflow file for ``runtime``, byte for byte what setup writes."""
50
+ template = (resources.files(__package__) / WORKFLOW_TEMPLATE).read_text(encoding="utf-8")
51
+ return template.replace("{runtime}", runtime)
52
+
53
+
54
+ # --- capture -------------------------------------------------------------------
55
+
56
+
57
+ def default_agent() -> str:
58
+ """Who is capturing: ``$AGENT_MEMORY_AGENT`` (the hook exports it), else the user, else ``unknown``."""
59
+ return os.environ.get(DEFAULT_AGENT_ENV) or os.environ.get("USER") or "unknown"
60
+
61
+
62
+ def default_runtime() -> str:
63
+ """The runtime reading: ``$AGENT_MEMORY_AGENT`` when it names one, else claude."""
64
+ agent = os.environ.get(DEFAULT_AGENT_ENV)
65
+ return agent if agent in startup.RUNTIMES else startup.RUNTIME_CLAUDE
66
+
67
+
68
+ def format_entry(decision: str, *, why: Optional[str], agent: str, source: Optional[str], now: dt.datetime) -> str:
69
+ """One decision as a list line: the decision, its reason, then the provenance in parentheses."""
70
+ text = _one_line(decision)
71
+ if not text:
72
+ raise WorkflowError("the decision is empty")
73
+ parts = [f"- **{text}**"]
74
+ reason = _one_line(why or "")
75
+ if reason:
76
+ parts.append(f" Why: {reason}")
77
+ provenance = [_one_line(agent) or "unknown", _iso(now)]
78
+ reference = _one_line(source or "")
79
+ if reference:
80
+ provenance.append(f"source: {reference}")
81
+ parts.append(f" ({', '.join(provenance)})")
82
+ return "".join(parts)
83
+
84
+
85
+ def capture(
86
+ home: Path,
87
+ project: str,
88
+ decision: str,
89
+ *,
90
+ why: Optional[str] = None,
91
+ source: Optional[str] = None,
92
+ agent: Optional[str] = None,
93
+ now: Optional[dt.datetime] = None,
94
+ dry_run: bool = False,
95
+ ) -> Dict[str, Any]:
96
+ """Append ``decision`` to the project's decision log, newest first under today's UTC heading."""
97
+ home = Path(home).expanduser().absolute()
98
+ layout.validate_project_name(project)
99
+ memory = layout.project_memory_dir(home, project)
100
+ path = memory / DECISION_FILE
101
+ moment = now or dt.datetime.now(dt.timezone.utc)
102
+ agent_name = agent or default_agent()
103
+ entry = format_entry(decision, why=why, agent=agent_name, source=source, now=moment)
104
+ date = _iso(moment)[:10]
105
+
106
+ link = _linked_component(home, path)
107
+ if link is not None:
108
+ raise WorkflowError(f"{link} is a symlink; the workflow never writes through a link")
109
+ if not memory.is_dir():
110
+ raise WorkflowError(
111
+ f"project {project!r} has no memory tier at {memory}; create it with `agent-memory init --project {project}`"
112
+ )
113
+ try:
114
+ info = os.lstat(path)
115
+ except FileNotFoundError:
116
+ raise WorkflowError(f"{path} is missing; recreate it with `agent-memory init --project {project}`") from None
117
+ except OSError as exc:
118
+ raise WorkflowError(f"cannot inspect {path}: {exc}") from exc
119
+ if not stat_mod.S_ISREG(info.st_mode):
120
+ raise WorkflowError(f"{path} is not a regular file")
121
+ try:
122
+ before = path.read_bytes().decode("utf-8")
123
+ except (OSError, UnicodeDecodeError) as exc:
124
+ raise WorkflowError(f"cannot read {path}: {exc}") from exc
125
+
126
+ after = insert_entry(before, date, entry)
127
+ result = {
128
+ "schema_version": SCHEMA_VERSION,
129
+ "action": "capture",
130
+ "home": str(home),
131
+ "project": project,
132
+ "path": str(path),
133
+ "date": date,
134
+ "entry": entry,
135
+ "agent": agent_name,
136
+ "dry_run": dry_run,
137
+ "written": False,
138
+ }
139
+ if dry_run:
140
+ return result
141
+ _replace_text(path, after, mode=stat_mod.S_IMODE(info.st_mode))
142
+ result["written"] = True
143
+ return result
144
+
145
+
146
+ def insert_entry(text: str, date: str, entry: str) -> str:
147
+ """``text`` with ``entry`` as the first item under the ``## date`` heading, creating the heading newest-first."""
148
+ lines = text.split("\n")
149
+ if lines and lines[-1] == "":
150
+ lines.pop() # the trailing newline; restored below
151
+ first = next((index for index, line in enumerate(lines) if _HEADING.match(line)), None)
152
+ if first is not None and _HEADING.match(lines[first]).group(1) == date:
153
+ at = first + 1
154
+ if at < len(lines) and lines[at] == "":
155
+ at += 1
156
+ lines[at:at] = [entry]
157
+ else:
158
+ block = [f"## {date}", "", entry, ""]
159
+ if first is None:
160
+ if lines and lines[-1] != "":
161
+ lines.append("")
162
+ lines.extend(block[:-1])
163
+ else:
164
+ at = first
165
+ if at > 0 and lines[at - 1] != "":
166
+ block.insert(0, "")
167
+ lines[at:at] = block
168
+ return "\n".join(lines) + "\n"
169
+
170
+
171
+ def _linked_component(home: Path, path: Path) -> Optional[Path]:
172
+ """The first symlink below ``home`` on the way down to ``path`` (``path`` included), or ``None``.
173
+
174
+ The home itself may be a link (a workspace marker is one); every component
175
+ beneath it is inspected without following, so a linked ``projects/``,
176
+ project or memory directory is refused before anything is read or staged.
177
+ A component that does not exist ends the walk: the later checks name it.
178
+ """
179
+ current = home
180
+ for part in path.relative_to(home).parts:
181
+ current = current / part
182
+ try:
183
+ info = os.lstat(current)
184
+ except (FileNotFoundError, NotADirectoryError):
185
+ return None
186
+ except OSError as exc:
187
+ raise WorkflowError(f"cannot inspect {current}: {exc}") from exc
188
+ if stat_mod.S_ISLNK(info.st_mode):
189
+ return current
190
+ return None
191
+
192
+
193
+ def _replace_text(path: Path, text: str, *, mode: int) -> None:
194
+ """Write ``text`` beside ``path`` and move it into place atomically; the file keeps its mode."""
195
+ data = text.encode("utf-8")
196
+ temp = path.with_name(f".{path.name}.{secrets.token_hex(4)}.tmp")
197
+ flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0)
198
+ try:
199
+ fd = os.open(temp, flags, mode)
200
+ except OSError as exc:
201
+ raise WorkflowError(f"cannot stage the write beside {path}: {exc}") from exc
202
+ try:
203
+ with os.fdopen(fd, "wb") as handle:
204
+ os.fchmod(handle.fileno(), mode) # os.open applied the umask; the original bits win
205
+ handle.write(data)
206
+ handle.flush()
207
+ os.fsync(handle.fileno())
208
+ os.replace(temp, path)
209
+ except OSError as exc:
210
+ try:
211
+ os.unlink(temp)
212
+ except OSError:
213
+ pass
214
+ raise WorkflowError(f"cannot write {path}: {exc}") from exc
215
+
216
+
217
+ # --- recall --------------------------------------------------------------------
218
+
219
+
220
+ def recall(
221
+ home: Path,
222
+ *,
223
+ project: Optional[str],
224
+ runtime: str = startup.RUNTIME_CLAUDE,
225
+ max_chars: int = startup.DEFAULT_MAX_CHARS,
226
+ home_source: str = "flag",
227
+ project_source: Optional[str] = None,
228
+ notes: List[str] = (),
229
+ ) -> Dict[str, Any]:
230
+ """The bounded read: the manifest's readable files, in its order, with their content, cut at ``max_chars``."""
231
+ manifest = startup.build_manifest(
232
+ home, runtime=runtime, project=project, home_source=home_source, project_source=project_source, notes=notes
233
+ )
234
+ parts: List[str] = []
235
+ files: List[Dict[str, Any]] = []
236
+ head = f"agent-memory recall: home {manifest['home']}"
237
+ head += f", project {manifest['project']}" if manifest["project"] is not None else ", no project"
238
+ parts.append(head + f"; content follows in manifest order, cut at {max_chars} characters.")
239
+ for entry in manifest["files"]:
240
+ row = {"tier": entry["tier"], "relative": entry["relative"], "state": entry["state"], "bytes": entry["bytes"]}
241
+ if entry["state"] == startup.READABLE:
242
+ try:
243
+ content = Path(entry["path"]).read_bytes().decode("utf-8", errors="replace")
244
+ except OSError as exc:
245
+ row["state"] = startup.UNREADABLE
246
+ row["error"] = exc.strerror or str(exc)
247
+ manifest["warnings"].append(f"{entry['relative']}: unreadable ({row['error']})")
248
+ else:
249
+ parts.append(f"\n--- {entry['relative']} ({entry['bytes']} bytes, modified {entry['modified_at_utc']}) ---")
250
+ parts.append(content.rstrip("\n"))
251
+ files.append(row)
252
+ parts.append(f"\nExcluded by default: {', '.join(manifest['excluded'])}")
253
+ if manifest["warnings"]:
254
+ parts.append("Warnings:")
255
+ parts.extend(f" - {warning}" for warning in manifest["warnings"])
256
+ text = _bound("\n".join(parts) + "\n", max_chars)
257
+ return {
258
+ "schema_version": SCHEMA_VERSION,
259
+ "action": "recall",
260
+ "home": manifest["home"],
261
+ "project": manifest["project"],
262
+ "content_injected": True,
263
+ "max_chars": max_chars,
264
+ "files": files,
265
+ "excluded": manifest["excluded"],
266
+ "warnings": manifest["warnings"],
267
+ "truncated": len(text) < len("\n".join(parts)) + 1,
268
+ "text": text,
269
+ }
270
+
271
+
272
+ def _bound(text: str, max_chars: int) -> str:
273
+ max_chars = max(0, max_chars)
274
+ if len(text) <= max_chars:
275
+ return text
276
+ suffix = f"\n[agent-memory: recall cut at {max_chars} characters; raise --max-chars or read the remaining files directly]\n"
277
+ if len(suffix) >= max_chars:
278
+ suffix = "[cut]\n"[:max_chars]
279
+ return text[: max_chars - len(suffix)] + suffix
280
+
281
+
282
+ def _one_line(text: str) -> str:
283
+ return _SPACE.sub(" ", text).strip()
284
+
285
+
286
+ def _iso(moment: dt.datetime) -> str:
287
+ return moment.astimezone(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
@@ -0,0 +1,261 @@
1
+ Metadata-Version: 2.5
2
+ Name: agent-memory-cli
3
+ Version: 0.1.0
4
+ Summary: Cross-session memory for coding agents: plain files, git-native, no server
5
+ Project-URL: Homepage, https://github.com/kiloloop/agent-memory
6
+ Project-URL: Source, https://github.com/kiloloop/agent-memory
7
+ Project-URL: Changelog, https://github.com/kiloloop/agent-memory/blob/main/CHANGELOG.md
8
+ Author: Kiloloop
9
+ License-Expression: Apache-2.0
10
+ License-File: LICENSE
11
+ Keywords: agents,git,markdown,memory
12
+ Classifier: Development Status :: 2 - Pre-Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development
23
+ Requires-Python: >=3.10
24
+ Provides-Extra: dev
25
+ Requires-Dist: build<2,>=1.2; extra == 'dev'
26
+ Requires-Dist: pytest<10,>=8; extra == 'dev'
27
+ Requires-Dist: ruff<1,>=0.11; extra == 'dev'
28
+ Requires-Dist: twine<8,>=7; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # agent-memory
32
+
33
+ Cross-session memory for coding agents — plain files, git-native, no server.
34
+
35
+ ## Why
36
+
37
+ Agents start sessions blank. Keep facts, decisions, and unfinished work in
38
+ Markdown you can read and edit.
39
+
40
+ Memory travels **between agents** via project files, **between projects** via
41
+ org rules, and **between machines** via optional git sync. Claude Code and Codex
42
+ share the store. No database, daemon, server, or runtime Python dependencies.
43
+
44
+ ## Quick Start
45
+
46
+ First time: install and bind. Every session after: recall context, capture decisions.
47
+
48
+ ### With your coding agent
49
+
50
+ Paste this into Claude Code or Codex from your repository:
51
+
52
+ ```text
53
+ Set up cross-session memory with agent-memory-cli from
54
+ https://pypi.org/project/agent-memory-cli/ and its source,
55
+ https://github.com/kiloloop/agent-memory.
56
+
57
+ Install with Python 3.10+. Ask for my home and project name, initialize and
58
+ bind this repository, and git-ignore the binding. Set up my runtime's hook
59
+ and workflow; report conflicts.
60
+
61
+ Capture a decision I provide, with its reason and runtime. Show status and
62
+ doctor. Next session here, recall context and report any truncation or sync
63
+ warning. Ask for my remote before enabling optional sync; keep pushing explicit.
64
+ ```
65
+
66
+ ### Manually
67
+
68
+ Python 3.10+ is required; sync needs git 2.25+, hooks use Bash, and
69
+ `archive`/`restore` are POSIX-only.
70
+
71
+ 1. **Install** with `uv tool install agent-memory-cli` or
72
+ `python -m pip install agent-memory-cli`. The distribution and the command
73
+ differ: installing `agent-memory-cli` gives you `agent-memory`. Put it on
74
+ your agent's PATH.
75
+ 2. **Bind**, from your repository root:
76
+
77
+ ```bash
78
+ agent-memory init --home "$HOME/agent-memory" --project my-app --repo .
79
+ ```
80
+
81
+ Git-ignore `.agent-memory.json`. Stay here; unset `AGENT_MEMORY_HOME`/`OACP_HOME` or point them at this
82
+ home: they precede the binding.
83
+ 3. **Set up** the runtime you use, then enable the hook there if required:
84
+
85
+ ```bash
86
+ agent-memory setup claude
87
+ # Or:
88
+ agent-memory setup codex
89
+ ```
90
+
91
+ 4. **Record now; recall next session** in this repository:
92
+
93
+ ```bash
94
+ agent-memory capture "Use SQLite for the cache." --why "no daemon" --agent codex
95
+ agent-memory recall
96
+ agent-memory status
97
+ agent-memory doctor
98
+ ```
99
+
100
+ Use `--agent claude` if appropriate; recall also works now for inspection.
101
+ 5. **Optionally sync.** Use an empty private remote. `enable --remote` pushes
102
+ the initial commit; substitute its URL:
103
+
104
+ ```bash
105
+ agent-memory enable --remote git@github.com:YOUR_ORG/agent-memory-store.git
106
+ agent-memory push --agent codex
107
+ # After another machine pushes, with a clean local tree:
108
+ agent-memory pull
109
+ ```
110
+
111
+ Elsewhere, `agent-memory clone <remote-url> --home <local-home>`, then repeat
112
+ binding and setup. Bind each machine separately; push explicitly.
113
+
114
+ ## How It Works
115
+
116
+ ![Four project memory files with sample Markdown](https://raw.githubusercontent.com/kiloloop/agent-memory/main/docs/images/project-memory.jpg)
117
+
118
+ *Illustrative files; not a bundled application UI.*
119
+
120
+ OACP defines the layout; this tool implements it. The four active files in
121
+ `projects/<name>/memory/` hold notes such as these trimmed samples:
122
+
123
+ | File | Purpose and sample |
124
+ | --- | --- |
125
+ | `project_facts.md` | Stable facts: FastAPI backend; Postgres; no PII in logs. |
126
+ | `decision_log.md` | Choices: 2026-05-09 — retry 5xx three times, with backoff and jitter. |
127
+ | `open_threads.md` | Work and owners: OAuth refresh race — waiting on Codex. |
128
+ | `known_debt.md` | Problems: replace the hard-coded session TTL with a setting. |
129
+
130
+ Date entries; supersede decisions by adding new ones. Close or pause threads,
131
+ write for humans, and distill transcripts. Edit other files directly; promote
132
+ debt to a thread when work starts.
133
+
134
+ `org-memory/` sits beside `projects/`: `recent.md`, `decisions.md`, and
135
+ `rules.md` carry shared context; `events/` and `debriefs/` hold records.
136
+ Most notes belong to a project. Use org memory only across repositories.
137
+
138
+ Home resolution: `--home` → `AGENT_MEMORY_HOME` → `OACP_HOME` → nearest ancestor
139
+ `.agent-memory.json` → workspace marker → `~/agent-memory`. A workspace marker
140
+ points into a home's `projects/` tree. Project selection uses `--project` or a
141
+ matching binding/marker. OACP is not required.
142
+
143
+ Sync makes the home a git repository with an allowlist and `.oacp-memory-repo`
144
+ marker. `push` commits selected paths; `pull` fast-forwards a clean tree that
145
+ is not ahead or diverged. No merges; keys and setup receipts stay local.
146
+ Network verbs time out after 30 seconds.
147
+
148
+ `setup` installs a SessionStart hook and memory workflow. `startup` lists
149
+ metadata for the four project files, then the three curated org files; it
150
+ injects no content. `--pull` refreshes first, warning on failure. The workflow
151
+ tells the agent to run `recall` for an 8,000-character bounded read. Raise
152
+ `--max-chars` or read remaining files directly when cut. `capture` records
153
+ decisions during work. At the end, update threads and debt, optionally publish
154
+ a summary with `debrief write`, and explicitly `push`; no push hook is installed.
155
+
156
+ Startup and recall exclude `archive/`, `events/`, and `debriefs/`. This is not
157
+ a vector database, RAG pipeline, or chat-history store: no embeddings,
158
+ similarity queries, synthesis, or indexing. Recall reads a fixed file set.
159
+
160
+ ### Commands
161
+
162
+ | Command | Purpose |
163
+ | --- | --- |
164
+ | [`status`][status] | Inspect home and sync. |
165
+ | [`doctor`][doctor] | Check health; repair nothing. |
166
+ | [`init`][init] | Scaffold and bind. |
167
+ | [`org init`][init] | Scaffold org memory. |
168
+ | [`enable`][sync] | Enable git sync. |
169
+ | [`clone`][sync] | Clone a memory remote. |
170
+ | [`pull`][sync] | Fast-forward from upstream. |
171
+ | [`push`][sync] | Commit selected files and push. |
172
+ | [`disable`][sync] | Disable sync. |
173
+ | [`archive`][archive-and-restore] | Archive a supplementary file. |
174
+ | [`restore`][archive-and-restore] | Restore to an empty slot. |
175
+ | [`setup`][setup] | Install runtime integration. |
176
+ | [`startup`][startup] | Print the metadata manifest. |
177
+ | [`capture`][capture-and-recall] | Record a decision. |
178
+ | [`recall`][capture-and-recall] | Read bounded context. |
179
+ | [`debrief write`][debrief-write] | Publish a session summary. |
180
+
181
+ [status]: https://github.com/kiloloop/agent-memory/blob/main/docs/commands.md#status
182
+ [doctor]: https://github.com/kiloloop/agent-memory/blob/main/docs/commands.md#doctor
183
+ [init]: https://github.com/kiloloop/agent-memory/blob/main/docs/commands.md#init
184
+ [sync]: https://github.com/kiloloop/agent-memory/blob/main/docs/commands.md#sync
185
+ [archive-and-restore]: https://github.com/kiloloop/agent-memory/blob/main/docs/commands.md#archive-and-restore
186
+ [setup]: https://github.com/kiloloop/agent-memory/blob/main/docs/commands.md#setup
187
+ [startup]: https://github.com/kiloloop/agent-memory/blob/main/docs/commands.md#startup
188
+ [capture-and-recall]: https://github.com/kiloloop/agent-memory/blob/main/docs/commands.md#capture-and-recall
189
+ [debrief-write]: https://github.com/kiloloop/agent-memory/blob/main/docs/commands.md#debrief-write
190
+
191
+ ## Examples
192
+
193
+ Scratch run in `/private/tmp/am-readme-demo`: init/startup/recall excerpts;
194
+ other outputs complete.
195
+
196
+ ```console
197
+ $ agent-memory init --home memory --project demo
198
+ Initialized memory home: /private/tmp/am-readme-demo/memory
199
+ ```
200
+
201
+ ```console
202
+ $ agent-memory startup --home memory --project demo --runtime claude --max-chars 420
203
+ agent-memory startup (claude): home /private/tmp/am-readme-demo/memory (flag), project demo (flag)
204
+ Project memory, read in this order (states are readability only; no content is injected):
205
+ ```
206
+
207
+ ```console
208
+ $ agent-memory capture 'Use SQLite for the cache.' --why 'no daemon' --agent codex --home memory --project demo
209
+ captured: /private/tmp/am-readme-demo/memory/projects/demo/memory/decision_log.md (## 2026-09-07)
210
+ - **Use SQLite for the cache.** Why: no daemon (codex, 2026-09-07T01:53:42Z)
211
+ ```
212
+
213
+ ```console
214
+ $ agent-memory recall --home memory --project demo --max-chars 850
215
+ ## 2026-09-07
216
+
217
+ - **Use SQLite for the cache.** Why: no daemon (codex, 2026-09-07T01:53:42Z)
218
+ ```
219
+
220
+ ```console
221
+ $ agent-memory status --home memory
222
+ home: memory
223
+ source: flag
224
+ exists: yes
225
+ marker: absent
226
+ gitignore: canonical
227
+ org-memory: present
228
+ projects: 1 with a memory dir
229
+ sync: not configured
230
+ ```
231
+
232
+ ```console
233
+ $ agent-memory doctor --home memory
234
+ [+] Org Memory
235
+ [+] org-memory/debriefs/ — present
236
+ [+] debriefs/ — empty store, nothing to validate
237
+
238
+ [-] Memory Sync
239
+ [-] .oacp-memory-repo — not configured; memory sync hooks are disabled
240
+ Run: agent-memory enable [--remote URL]
241
+
242
+ No issues found.
243
+ ```
244
+
245
+ ## Project
246
+
247
+ - [PyPI package: agent-memory-cli](https://pypi.org/project/agent-memory-cli/)
248
+ - [Source](https://github.com/kiloloop/agent-memory)
249
+ - [OACP](https://github.com/kiloloop/oacp)
250
+
251
+ ## License
252
+
253
+ Apache-2.0. See [LICENSE](https://github.com/kiloloop/agent-memory/blob/main/LICENSE).
254
+
255
+ ## Development
256
+
257
+ Activate `.venv` before running the two `make` commands.
258
+
259
+ python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
260
+ make preflight # lint, test, build
261
+ make wheel-check # install the built wheel in a throwaway venv and prove it operates a home
@@ -0,0 +1,33 @@
1
+ agent_memory/__init__.py,sha256=--LT_n5IN-qMqQNISjOU9x0avYFWh51_XwsL8pTPAYU,316
2
+ agent_memory/__main__.py,sha256=XNI_oqFGNB-_kBZ2_GgnTA43aWGFFftC-rjgEvEwrmM,215
3
+ agent_memory/archive.py,sha256=ABSpaii9ONDxyeaFAqjEDttrIjpFJBZAvrdKWR436jc,15737
4
+ agent_memory/cli.py,sha256=4NiqRFt0ZG2x3efww6fc9yQzq6P2YSoI-Y3fa_5E6oQ,19840
5
+ agent_memory/debrief.py,sha256=mWwPXTXRA7lbYzIk4e4qrUlXu34RZCAaEV1CeY_swSg,12447
6
+ agent_memory/doctor.py,sha256=_m9olIf90dNrJv8tFhSuquhMRdyfiaYtqOicNJV6EMQ,27913
7
+ agent_memory/git_runner.py,sha256=Uk4rGOx2NXoYmY__wUAuKR36dZWSQ-w_LpsI-jO26WY,2184
8
+ agent_memory/home.py,sha256=x3HpOh078AsERKS85ZhVzlCB1v_PCvWyUJlKsionBbQ,9724
9
+ agent_memory/layout.py,sha256=wq8kmXgn7cm0boou2Q9YJFsgEKcFUDtrwhg6iDI5TF4,7153
10
+ agent_memory/org.py,sha256=fjIB_J5uEHqZzLW3mYDL0m1BkSKHrTt11XQbNiN-CMc,8874
11
+ agent_memory/publication.py,sha256=AuZI-cCIKjmtqb_aUOAxEWlxOUrY8Xa1HGPApiGuYLE,19436
12
+ agent_memory/startup.py,sha256=Ks_8svX1XVaWnhMUlOHiKVWexX7-Eyq_EoqEnF5pppg,10697
13
+ agent_memory/status.py,sha256=r63nhpVlJXPeIxsvLABYhfAGjymx3WxOuHR78991-gM,5198
14
+ agent_memory/sync.py,sha256=EbGCPbFTtF159pxtyCnWOEj6z8ho1tnhLflnrGjYP4Y,26429
15
+ agent_memory/workflow.py,sha256=C7A9YFIN_tnr2M3WC4CIP1jfU_4_4czrb89dxIxJIVQ,11169
16
+ agent_memory/setup/__init__.py,sha256=ZRdh5ZKPqud2PRr2QGzLIiybeHSEFZKG79S0rhgYF4M,1048
17
+ agent_memory/setup/claude.py,sha256=w-3SgFiiE1hXc-rnVqIRGoAoYZ4gADsji2RkD-kQ1LY,1371
18
+ agent_memory/setup/codex.py,sha256=pZt4Ff7Qc5-WO12jlWjJb1-RIyT70HFIEzwb0K8YHOA,1702
19
+ agent_memory/setup/common.py,sha256=m1IgTRVDArh6WEGkcKPaNWsxNWxwaCOgt1WN4AUvOhM,40314
20
+ agent_memory/setup/legacy.py,sha256=50vqi_uhtdedl5P29VPQdm6yqnI10aFUmSFtgra1KJY,2198
21
+ agent_memory/templates/org-memory/decisions.md,sha256=Et5wl18CjH467IxFt4mZZLqd49iVJo_yW4KgoKUmNuo,287
22
+ agent_memory/templates/org-memory/recent.md,sha256=cf4gcoXaSpa914c69LGPrRZ61QvbIeSYt-hnFPQjpU0,389
23
+ agent_memory/templates/org-memory/rules.md,sha256=OpmFL4_ZSeb8mRjC8C607FHZXdcM2d8768mXGrO_VPY,283
24
+ agent_memory/templates/project-memory/decision_log.md,sha256=nNmLH635rBi7p26d8YsLcQfXE9ExyLHK4oNj8jvrWi8,96
25
+ agent_memory/templates/project-memory/known_debt.md,sha256=jM5CVGtAfheKjUK4eFA1eSaaE7yLBm9YlmGNHxDUGIY,240
26
+ agent_memory/templates/project-memory/open_threads.md,sha256=0FgTjhFLaZXy19tyJceCUQE_QY-8Lr48Vd0VumbkzRQ,28
27
+ agent_memory/templates/project-memory/project_facts.md,sha256=UIX7_vRJ-tQ5AIT_w1Lzj6n1jhBY4VxyPCLUBfA1-Jg,196
28
+ agent_memory/templates/workflow/SKILL.md,sha256=4GLaQP0xbjrDBy5vDJbnokv7wOgaHVT0SZsQlZkVqco,1980
29
+ agent_memory_cli-0.1.0.dist-info/METADATA,sha256=oXIHdljPWWKZyxlk5sMKeh6yEh_RCCkJjz-photRJwE,10379
30
+ agent_memory_cli-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
31
+ agent_memory_cli-0.1.0.dist-info/entry_points.txt,sha256=mG_BjBbd8UY1fAkTlcl2sByYvElm35FtvqUziRuI96Q,55
32
+ agent_memory_cli-0.1.0.dist-info/licenses/LICENSE,sha256=MeRxiQ3KgmBtsOGLgCkYhT5P-aoktQxag0v5CwePUMs,11338
33
+ agent_memory_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ agent-memory = agent_memory.cli:main