Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: ${{ matrix.python-version }}
cache: pip
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: "3.12"
cache: pip
Expand All @@ -40,7 +40,7 @@ jobs:
test "${RELEASE_TAG}" = "v${PACKAGE_VERSION}"

- name: Upload release artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: python-package-distributions
path: dist/
Expand All @@ -57,13 +57,13 @@ jobs:

steps:
- name: Download release artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: python-package-distributions
path: dist/

- name: Publish distributions to PyPI with attestations
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
with:
attestations: true

Expand All @@ -78,7 +78,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Download release artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: python-package-distributions
path: dist/
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ All notable changes to agent-memory are documented in this file. The format
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.1.5 - 2026-11-02

### Added

- An `agent-memory` skill teaches agents when to recall, capture, archive and sync, matched to its release ([skill](skills/agent-memory/README.md)).
- Python 3.14 is declared in the package classifiers; CI has tested it since 0.1.4 ([pyproject](pyproject.toml)).

### Changed

- `setup` no longer writes a workflow skill into the repository and leaves an existing copy as it is ([setup](docs/commands.md#setup)).

## 0.1.4 - 2026-10-26

### Fixed
Expand Down
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ https://github.com/kiloloop/agent-memory.

Install with Python 3.10+. Ask for my home and project name, initialize and
bind this repository, and git-ignore the binding. Set up my runtime's hook
and workflow; report conflicts.
and install the source's skills/agent-memory skill; report conflicts.

Capture a decision I provide, with its reason and runtime. Show status and
doctor. Next session here, recall context and report any truncation or sync
Expand Down Expand Up @@ -58,6 +58,9 @@ Python 3.10+ is required; sync needs git 2.25+, hooks use Bash, and
agent-memory setup codex
```

Then copy the source's [`skills/agent-memory/`][skill] into your agent's
skills directory; it teaches the workflow below.

4. **Record now; recall next session** in this repository:

```bash
Expand Down Expand Up @@ -116,10 +119,10 @@ and deliver. A plain `pull` updates a clean tree and reports unpushed commits.
Conflicts abort the rebase and retain local commits. Keys and setup receipts
stay local. Network and rebase operations time out after 30 seconds.

`setup` installs a SessionStart hook and memory workflow. `startup` lists
`setup` installs a SessionStart hook. `startup` lists
metadata for the four project files, then the three curated org files; it
injects no content. `--pull` commits and syncs first under the runtime identity, warning on failure. The workflow
tells the agent to run `recall` for an 8,000-character bounded read. Raise
injects no content. `--pull` commits and syncs first under the runtime identity, warning on failure. The
[`agent-memory` skill][skill] tells the agent to run `recall` for an 8,000-character bounded read. Raise
`--max-chars` or read remaining files directly when cut. `capture` records
decisions during work. At the end, update threads and debt, optionally publish
a summary with `debrief write`, and explicitly `push`; no push hook is installed.
Expand Down Expand Up @@ -150,6 +153,7 @@ similarity queries, synthesis, or indexing. Recall reads a fixed file set.
| [`debrief write`][debrief-write] | Publish a session summary. |
| [`event write`][event-write] | Publish an org-memory event. |

[skill]: https://github.com/kiloloop/agent-memory/tree/main/skills/agent-memory
[status]: https://github.com/kiloloop/agent-memory/blob/main/docs/commands.md#status
[doctor]: https://github.com/kiloloop/agent-memory/blob/main/docs/commands.md#doctor
[init]: https://github.com/kiloloop/agent-memory/blob/main/docs/commands.md#init
Expand Down
13 changes: 6 additions & 7 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,11 @@ wrote, and no symlink lies between the repository and the file. Custom
entries are untouched; the codex entry sits beside the kernel's
session-init entry and retires only its pull flag, edited in place in one
simple command (a compound command is left as written and named). No push
hook is ever installed. Beside the hook, `setup` installs the runtime's memory
workflow file, a repository skill (`.claude/skills/agent-memory/SKILL.md`,
`.agents/skills/agent-memory/SKILL.md`) rendered from one shipped text with
the runtime's name and managed by the hook's rules: written once, regenerated
only while its digest is a shipped template, kept and named as a conflict when
edited, never written through a symlink, and recorded in the receipt.
hook is ever installed. `setup` installs no skill: the workflow is taught by
the [`agent-memory` skill](../skills/agent-memory/README.md), installed
separately. Release 0.1.4 and earlier also wrote a workflow file
(`.claude/skills/agent-memory/SKILL.md`, `.agents/skills/agent-memory/SKILL.md`);
`setup` now leaves an existing copy as it is and no longer records it.

## Startup

Expand All @@ -174,7 +173,7 @@ different home lends no project, and says so in the warnings.

## Capture and recall

`capture` and `recall` are the workflow that file describes. `capture "<decision>"
`capture` and `recall` are the workflow the skill describes. `capture "<decision>"
[--why TEXT] [--source REF] [--agent NAME]` appends one decision to the
project's `decision_log.md`, newest first under today's UTC date heading, with
its provenance (the agent, the UTC time, the source); it touches no other
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Software Development",
]

Expand Down Expand Up @@ -55,6 +56,7 @@ include = [
"/Makefile",
"/README.md",
"/scripts",
"/skills",
"/src",
"/tests",
]
Expand Down
22 changes: 19 additions & 3 deletions scripts/check_installed_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

Run ``make build`` first. Each step is fatal on failure:

1. create a throwaway venv and install the newest ``dist/*.whl`` plus pytest, no extras;
1. create a throwaway venv and install the newest ``dist/*.whl`` with no extras, then the
pytest requirement the wheel's own ``dev`` extra declares (pyproject.toml stays the one
declaration of that bound);
2. assert the installed distribution declares no runtime dependencies and that the
kernel package this tool is leaving is not importable in that venv;
3. with AGENT_MEMORY_HOME and OACP_HOME stripped from the environment, scaffold a fresh
Expand All @@ -29,6 +31,7 @@
import hashlib
import json
import os
import re
import shutil
import subprocess
import sys
Expand All @@ -47,6 +50,17 @@
)
ORG_FILES = ("recent.md", "decisions.md", "rules.md")
PROJECT_FILES = ("project_facts.md", "decision_log.md", "open_threads.md", "known_debt.md")
DEV_EXTRA = re.compile(r"""extra\s*==\s*["']dev["']""")


def dev_requirement(declared: Sequence[str], name: str) -> str:
"""Return the wheel's ``dev``-extra requirement for ``name``, marker stripped (``pytest<10,>=8``)."""
for entry in declared:
requirement, _, marker = entry.partition(";")
requirement = requirement.strip()
if DEV_EXTRA.search(marker) and re.match(rf"{re.escape(name)}(?![\w.-])", requirement, re.IGNORECASE):
return requirement
sys.exit(f"the wheel's dev extra declares no {name} requirement")


def newest_wheel() -> Path:
Expand Down Expand Up @@ -102,15 +116,17 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
env["PATH"] = os.pathsep.join([str(python.parent), env.get("PATH", "")])
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"

run([python, "-m", "pip", "install", "--quiet", wheel, "pytest"], env)
run([python, "-m", "pip", "install", "--quiet", wheel], env)

probe = run(
[python, "-c", f"import importlib.metadata as m, json; print(json.dumps(m.requires({DISTRIBUTION!r}) or []))"],
env,
)
runtime = [entry for entry in json.loads(probe.stdout) if "extra ==" not in entry]
declared = json.loads(probe.stdout)
runtime = [entry for entry in declared if "extra ==" not in entry]
if runtime:
sys.exit(f"the wheel declares runtime dependencies: {runtime}")
run([python, "-m", "pip", "install", "--quiet", dev_requirement(declared, "pytest")], env)
foreign = run(
[python, "-c", "import importlib.util as u; raise SystemExit(0 if u.find_spec('oacp') is None else 1)"],
env,
Expand Down
143 changes: 143 additions & 0 deletions skills/agent-memory/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# agent-memory skill

Teach a coding agent the `agent-memory` workflow: recall the project's
memory at session start, record the user's decisions as they are made, and
keep the memory home tidy, recorded and synced.

## What it is

The CLI does the work. The skill tells an agent which verb to run when, and
which uses to avoid:

- recall before acting, and capture only what the user decided;
- archive supplementary files, never the four active ones;
- treat debriefs and events as append-only records;
- push deliberately at the end of a session, knowing that a pull on a home
with local changes, the session-start hook's included, commits and
delivers them too.

It lives in the same repository as the CLI and ships in the same release, so
each copy describes the release it came from.

The claim it makes, and the repository's tests check: a decision captured in
one session is in the next session's `recall`, while the session-start
manifest lists that file without its content. Archived files and org events
stay out of that read, and an active memory file cannot be archived.

## What it looks like

The output below is real. It was captured against a scratch home, with
`--home` and `--project` given explicitly:

```console
$ agent-memory capture "Cache with SQLite." --why "no daemon to run" --source "PR 12" --agent claude --home /private/tmp/claude/agent-memory-skill/home --project demo
captured: /private/tmp/claude/agent-memory-skill/home/projects/demo/memory/decision_log.md (## 2026-11-01)
- **Cache with SQLite.** Why: no daemon to run (claude, 2026-11-01T03:28:50Z, source: PR 12)

$ agent-memory startup --runtime claude --home /private/tmp/claude/agent-memory-skill/home --project demo
agent-memory startup (claude): home /private/tmp/claude/agent-memory-skill/home (flag), project demo (flag)
Project memory, read in this order (states are readability only; no content is injected):
1. projects/demo/memory/project_facts.md: readable, 196 bytes, modified 2026-11-01T03:28:50Z
2. projects/demo/memory/decision_log.md: readable, 205 bytes, modified 2026-11-01T03:28:50Z
3. projects/demo/memory/open_threads.md: readable, 28 bytes, modified 2026-11-01T03:28:50Z
4. projects/demo/memory/known_debt.md: readable, 240 bytes, modified 2026-11-01T03:28:50Z
Org memory, curated context; consult what governs the work before doing it (not read by default):
5. org-memory/recent.md: readable, 389 bytes, modified 2026-11-01T03:28:50Z
6. org-memory/decisions.md: readable, 287 bytes, modified 2026-11-01T03:28:50Z
7. org-memory/rules.md: readable, 283 bytes, modified 2026-11-01T03:28:50Z
Excluded by default: org-memory/events/, org-memory/debriefs/, projects/demo/memory/archive/

$ agent-memory recall --home /private/tmp/claude/agent-memory-skill/home --project demo --max-chars 1100
agent-memory recall: home /private/tmp/claude/agent-memory-skill/home, project demo; content follows in manifest order, cut at 1100 characters.

--- projects/demo/memory/project_facts.md (196 bytes, modified 2026-11-01T03:28:50Z) ---
# Project Facts

<!-- Stable facts about this project: what it is, where it runs, who owns what. -->
<!-- Rewrite in place as facts change; the history of a change belongs in decision_log.md. -->

--- projects/demo/memory/decision_log.md (205 bytes, modified 2026-11-01T03:28:50Z) ---
# Decision Log

<!-- One dated entry per decision, newest first: what was decided, and why. -->

## 2026-11-01

- **Cache with SQLite.** Why: no daemon to run (claude, 2026-11-01T03:28:50Z, source: PR 12)

--- projects/demo/memory/open_threads.md (28 bytes, modified 2026-11-01T03:28:50Z) ---
# Open Threads

- None yet.

--- projects/demo/memory/known_debt.md (240 bytes, modified 2026-11-01T03:28:50Z) ---
# Known Debt

Use this file to track verified, unresolved project debt
[agent-memory: recall cut at 1100 characters; raise --max-chars or read the remaining files directly]

$ agent-memory archive demo decision_log.md --home /private/tmp/claude/agent-memory-skill/home
agent-memory: error: cannot archive standard active memory file: decision_log.md
```

The last command exits `1`. The cut notice is the signal the skill tells an
agent to act on: raise `--max-chars` or read the rest directly.

## Run it

Install the release this skill ships in, then copy this directory into your
agent's skills directory:

```bash
python -m pip install agent-memory-cli==0.1.5
mkdir -p ~/.claude/skills && cp -R skills/agent-memory ~/.claude/skills/ # Claude Code
mkdir -p ~/.agents/skills && cp -R skills/agent-memory ~/.agents/skills/ # Codex
```

`agent-memory --version` should print `agent-memory 0.1.5`. In a repository,
`agent-memory setup claude` or `setup codex` installs the session-start hook
that pulls the home and prints the manifest. The skill teaches the rest. The
full procedure is in [`SKILL.md`](SKILL.md).

## Choices worth knowing about

- **Released with the CLI.** The skill names the release it describes in its
install line, and the repository's tests fail when that disagrees with the
package version. `agent-memory --version` is the check: an older CLI needs
that release installed, a newer one needs the skill from its release.
- **One skill, one name.** `agent-memory setup` in release 0.1.4 also writes a
shorter workflow skill named `agent-memory` into the repository
(`.claude/skills/agent-memory/SKILL.md`, `.agents/skills/agent-memory/SKILL.md`).
Later releases stop writing it but never delete an existing copy. A copy left
from an earlier setup has the same name as this skill and can hide it, so
delete that copy when you install this one.
- **The hook syncs; the skill teaches.** The session-start hook keeps doing the
pull, and on a home with local changes that pull commits and delivers them.
The skill adds no hook of its own; `push` stays a deliberate end-of-session
step.
- **Runtime-neutral.** Commands take `--agent <claude|codex>` and, for
`recall`, `--runtime codex` from Codex. The skill does not bake in a runtime
name.

## What it cannot see

- Whether a remembered decision is still right, or whether a fact is true.
`recall` prints files; it does not judge them.
- Anything outside the fixed read set: supplementary files, `archive/`,
`events/` and `debriefs/` are read only when an agent opens them by name.
- Search, synthesis, embeddings or indexes. The CLI has none.
- Memory files whose structure has drifted from the templates. The skill says
where things go, not how to repair a file.

## Verify the skill

`tests/test_skill.py` runs with the rest of the suite (`make test`). It fails
when `SKILL.md` or this README names a version other than the one the package
releases, or shows a command or option the CLI does not have.

The behaviour the skill relies on is tested with the commands themselves:
capture then recall across two sessions and the recall exclusions in
`tests/test_workflow.py`, the content-free startup manifest and a session-start
pull that commits and delivers a local change in `tests/test_startup.py`,
archive refusal and round trip in `tests/test_archive.py`, and event records in
`tests/test_events.py`.
Loading
Loading