memware 0.2.1__tar.gz → 0.2.2__tar.gz
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.
- {memware-0.2.1 → memware-0.2.2}/CHANGELOG.md +10 -0
- {memware-0.2.1 → memware-0.2.2}/PKG-INFO +1 -1
- {memware-0.2.1 → memware-0.2.2}/docs/backup.md +6 -3
- {memware-0.2.1 → memware-0.2.2}/src/memware/__init__.py +1 -1
- {memware-0.2.1 → memware-0.2.2}/src/memware/backup.py +23 -12
- {memware-0.2.1 → memware-0.2.2}/tests/test_backup.py +48 -8
- {memware-0.2.1 → memware-0.2.2}/.claude-plugin/marketplace.json +0 -0
- {memware-0.2.1 → memware-0.2.2}/.github/CODEOWNERS +0 -0
- {memware-0.2.1 → memware-0.2.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {memware-0.2.1 → memware-0.2.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {memware-0.2.1 → memware-0.2.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {memware-0.2.1 → memware-0.2.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {memware-0.2.1 → memware-0.2.2}/.github/dependabot.yml +0 -0
- {memware-0.2.1 → memware-0.2.2}/.github/workflows/ci.yml +0 -0
- {memware-0.2.1 → memware-0.2.2}/.github/workflows/release.yml +0 -0
- {memware-0.2.1 → memware-0.2.2}/.gitignore +0 -0
- {memware-0.2.1 → memware-0.2.2}/.pre-commit-config.yaml +0 -0
- {memware-0.2.1 → memware-0.2.2}/CODE_OF_CONDUCT.md +0 -0
- {memware-0.2.1 → memware-0.2.2}/CONTRIBUTING.md +0 -0
- {memware-0.2.1 → memware-0.2.2}/LICENSE +0 -0
- {memware-0.2.1 → memware-0.2.2}/README.md +0 -0
- {memware-0.2.1 → memware-0.2.2}/RELEASING.md +0 -0
- {memware-0.2.1 → memware-0.2.2}/SECURITY.md +0 -0
- {memware-0.2.1 → memware-0.2.2}/docs/design.md +0 -0
- {memware-0.2.1 → memware-0.2.2}/docs/eval.md +0 -0
- {memware-0.2.1 → memware-0.2.2}/docs/integrations.md +0 -0
- {memware-0.2.1 → memware-0.2.2}/docs/keeping-memory-clean.md +0 -0
- {memware-0.2.1 → memware-0.2.2}/docs/upstream-hermes-pr.md +0 -0
- {memware-0.2.1 → memware-0.2.2}/eval/questions.example.jsonl +0 -0
- {memware-0.2.1 → memware-0.2.2}/integrations/claude-code/.claude-plugin/plugin.json +0 -0
- {memware-0.2.1 → memware-0.2.2}/integrations/claude-code/README.md +0 -0
- {memware-0.2.1 → memware-0.2.2}/integrations/claude-code/hooks/hooks.json +0 -0
- {memware-0.2.1 → memware-0.2.2}/integrations/hermes/README.md +0 -0
- {memware-0.2.1 → memware-0.2.2}/integrations/hermes/memware/__init__.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/integrations/hermes/memware/plugin.yaml +0 -0
- {memware-0.2.1 → memware-0.2.2}/integrations/hermes/upstream/README.md +0 -0
- {memware-0.2.1 → memware-0.2.2}/integrations/hermes/upstream/plugins/memory/memware/README.md +0 -0
- {memware-0.2.1 → memware-0.2.2}/integrations/hermes/upstream/plugins/memory/memware/__init__.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/integrations/hermes/upstream/plugins/memory/memware/config_schema.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/integrations/hermes/upstream/plugins/memory/memware/plugin.yaml +0 -0
- {memware-0.2.1 → memware-0.2.2}/integrations/hermes/upstream/tests/plugins/memory/test_memware_provider.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/integrations/hermes/upstream/website/docs/user-guide/features/memory-providers.memware.md +0 -0
- {memware-0.2.1 → memware-0.2.2}/pyproject.toml +0 -0
- {memware-0.2.1 → memware-0.2.2}/scripts/leak-check.sh +0 -0
- {memware-0.2.1 → memware-0.2.2}/src/memware/cli.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/src/memware/config.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/src/memware/eval.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/src/memware/index.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/src/memware/ingest/__init__.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/src/memware/ingest/claude_code.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/src/memware/ingest/generic.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/src/memware/ledger.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/src/memware/mcp_server.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/src/memware/passage.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/src/memware/py.typed +0 -0
- {memware-0.2.1 → memware-0.2.2}/src/memware/review.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/src/memware/store.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/tests/__init__.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/tests/conftest.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/tests/test_cli.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/tests/test_eval.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/tests/test_hermes_plugin.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/tests/test_hermes_upstream_plugin.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/tests/test_index.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/tests/test_ingest.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/tests/test_ledger.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/tests/test_passage.py +0 -0
- {memware-0.2.1 → memware-0.2.2}/tests/test_review.py +0 -0
|
@@ -6,6 +6,16 @@ All notable changes to this project are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.2] - 2026-09-03
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Backup retention now **promotes**: a snapshot ages forward through the 1/3/7/14-day tiers
|
|
13
|
+
instead of being pruned between them. The previous rule pruned every snapshot at age ~2, so a
|
|
14
|
+
daily-backup loop only ever kept a 0- and 1-day-old and never built a 3/7/14-day-old. Retention
|
|
15
|
+
keeps the newest plus the oldest per age band, always has a fresh ~1-day-old, drifts within
|
|
16
|
+
older tiers, prunes past the largest tier, and stays bounded (~5). Verified with a 30-day loop.
|
|
17
|
+
|
|
18
|
+
|
|
9
19
|
## [0.2.1] - 2026-09-03
|
|
10
20
|
|
|
11
21
|
## [0.2.0] - 2026-09-03
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: memware
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Memory for AI agents that only remembers the latest truth: a bi-temporal belief ledger plus a transcript index, in one SQLite file.
|
|
5
5
|
Project-URL: Homepage, https://github.com/ericwalisko/memware
|
|
6
6
|
Project-URL: Issues, https://github.com/ericwalisko/memware/issues
|
|
@@ -35,9 +35,12 @@ memware backup # writes memware-YYYYMMDD-HHMMSS.db, prunes to the retent
|
|
|
35
35
|
# and (by default) mirrors transcripts into <dest>/transcripts
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
**Retention
|
|
39
|
-
|
|
40
|
-
`
|
|
38
|
+
**Retention promotes.** Because memware only ever *creates* fresh snapshots, retention lets a
|
|
39
|
+
snapshot **age forward** through the tiers rather than pruning it in between: it keeps the newest
|
|
40
|
+
snapshot and the oldest one in each age band `(0,1] (1,3] (3,7] (7,14]`, so the same file serves
|
|
41
|
+
as the ~1-day-old, then the ~3-, ~7-, ~14-day-old as time passes. You always have a fresh
|
|
42
|
+
~1-day-old; the older tiers drift a little (that is fine and expected); anything past the largest
|
|
43
|
+
tier is pruned. The pile stays bounded (~5). Tune the tiers with `backup.keep_days`.
|
|
41
44
|
|
|
42
45
|
**Transcripts.** `backup.include_transcripts` (on by default) mirrors your transcript source
|
|
43
46
|
(default `~/.claude/projects`) into `<dest>/transcripts`, additively — an append-only archive
|
|
@@ -62,24 +62,35 @@ def newest_age_hours(dest_dir: str | os.PathLike[str]) -> float | None:
|
|
|
62
62
|
return (_now().timestamp() - _stamp(snaps[0]).timestamp()) / 3600.0
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
def _age_days(p: Path, now: datetime) -> float:
|
|
66
|
+
return (now.timestamp() - _stamp(p).timestamp()) / 86400.0
|
|
67
|
+
|
|
68
|
+
|
|
65
69
|
def apply_retention(dest_dir: str | os.PathLike[str], keep_days: list[int]) -> list[Path]:
|
|
66
|
-
"""
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
"""Promotion retention: since we only ever *create* fresh snapshots, a snapshot must be
|
|
71
|
+
allowed to **age forward** into the next tier rather than be pruned between tiers.
|
|
72
|
+
|
|
73
|
+
Bands are the contiguous intervals ``(0, d0], (d0, d1], …`` for sorted ``keep_days``
|
|
74
|
+
(default 1,3,7,14). We keep the newest snapshot overall (tomorrow's ~1-day-old) and the
|
|
75
|
+
*oldest* snapshot in each band — the oldest is the one on the leading edge, so as days
|
|
76
|
+
pass it crosses into the next band and stays kept, i.e. the same file serves as the ~1-,
|
|
77
|
+
then ~3-, ~7-, ~14-day-old. Everything else, and anything older than the largest tier,
|
|
78
|
+
is pruned. Result: always a ~1-day-old, roughly one per tier (they drift; that's fine),
|
|
79
|
+
and a bounded pile of ~len(keep_days)+1. Returns the deleted paths."""
|
|
80
|
+
snaps = list_snapshots(dest_dir) # newest first
|
|
71
81
|
if not snaps:
|
|
72
82
|
return []
|
|
73
83
|
now = _now()
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if
|
|
79
|
-
|
|
84
|
+
days = sorted(set(int(d) for d in keep_days))
|
|
85
|
+
keep: set[Path] = {snaps[0]} # always the freshest
|
|
86
|
+
edges = [0.0, *[float(d) for d in days]]
|
|
87
|
+
for lo, hi in zip(edges, edges[1:], strict=False):
|
|
88
|
+
band = [p for p in snaps if lo < _age_days(p, now) <= hi]
|
|
89
|
+
if band:
|
|
90
|
+
keep.add(band[-1]) # oldest in the band (snaps are newest-first) -> promotes forward
|
|
80
91
|
deleted = []
|
|
81
92
|
for p in snaps:
|
|
82
|
-
if p not in keep:
|
|
93
|
+
if p not in keep: # includes anything older than the largest tier
|
|
83
94
|
p.unlink(missing_ok=True)
|
|
84
95
|
deleted.append(p)
|
|
85
96
|
return deleted
|
|
@@ -31,20 +31,60 @@ def test_snapshot_is_consistent_and_restore_round_trips(tmp_path):
|
|
|
31
31
|
assert s.stats()["turns"] == 5
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def
|
|
34
|
+
def _age_all(dest):
|
|
35
|
+
"""Rename every snapshot one day older — how we advance the clock against the real _now()."""
|
|
36
|
+
import datetime as dt
|
|
37
|
+
|
|
38
|
+
for p in sorted(dest.glob("memware-*.db")):
|
|
39
|
+
stamp = bk._stamp(p) - dt.timedelta(days=1)
|
|
40
|
+
p.rename(dest / f"memware-{stamp.strftime('%Y%m%d-%H%M%S')}.db")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _ages(dest):
|
|
44
|
+
import datetime as dt
|
|
45
|
+
|
|
46
|
+
now = dt.datetime.now(dt.UTC)
|
|
47
|
+
return sorted((now - bk._stamp(p)).total_seconds() / 86400.0 for p in bk.list_snapshots(dest))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_retention_promotes_and_stays_bounded_over_a_daily_loop(tmp_path):
|
|
51
|
+
"""The real usage pattern: one new snapshot per day, retention each day. A snapshot must age
|
|
52
|
+
forward through the tiers (not be pruned between them), there is always a ~1-day-old, the pile
|
|
53
|
+
stays small, and nothing lingers far past the largest tier. Files are dated in real time and
|
|
54
|
+
aged by renaming, so this exercises the true `_now()`-based logic."""
|
|
35
55
|
import datetime as dt
|
|
36
56
|
|
|
37
57
|
dest = tmp_path / "b"
|
|
38
58
|
dest.mkdir()
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
stamp = (now - dt.timedelta(days=age)).strftime("%Y%m%d-%H%M%S")
|
|
59
|
+
for _day in range(30):
|
|
60
|
+
_age_all(dest) # yesterday's snapshots become a day older
|
|
61
|
+
stamp = dt.datetime.now(dt.UTC).strftime("%Y%m%d-%H%M%S") # a fresh age-0 today
|
|
43
62
|
(dest / f"memware-{stamp}.db").write_bytes(b"x")
|
|
63
|
+
bk.apply_retention(dest, [1, 3, 7, 14])
|
|
64
|
+
|
|
65
|
+
ages = _ages(dest)
|
|
66
|
+
assert min(ages) < 1 # always a fresh ~1-day-old
|
|
67
|
+
assert len(ages) <= 6 # bounded, not an unbounded pile
|
|
68
|
+
assert max(ages) <= 15 # nothing lingers far past the largest tier
|
|
69
|
+
assert any(2 <= a <= 9 for a in ages) # a snapshot promoted into the mid tiers (drift ok)
|
|
70
|
+
assert any(a >= 10 for a in ages) # and one aged into the oldest tier
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_retention_prunes_snapshots_older_than_the_largest_tier(tmp_path):
|
|
74
|
+
dest = tmp_path / "b"
|
|
75
|
+
dest.mkdir()
|
|
76
|
+
for age in (0, 2, 6, 13, 20, 40): # 20 and 40 are past the 14-day tier
|
|
77
|
+
_write_dated(dest, age)
|
|
44
78
|
bk.apply_retention(dest, [1, 3, 7, 14])
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
79
|
+
assert all(a <= 15 for a in _ages(dest))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _write_dated(dest, age_days):
|
|
83
|
+
import datetime as dt
|
|
84
|
+
|
|
85
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
stamp = (dt.datetime.now(dt.UTC) - dt.timedelta(days=age_days)).strftime("%Y%m%d-%H%M%S")
|
|
87
|
+
(dest / f"memware-{stamp}.db").write_bytes(b"x")
|
|
48
88
|
|
|
49
89
|
|
|
50
90
|
def test_backup_cli_uses_config_dest_and_mirrors_transcripts(tmp_path, capsys, monkeypatch):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memware-0.2.1 → memware-0.2.2}/integrations/hermes/upstream/plugins/memory/memware/README.md
RENAMED
|
File without changes
|
{memware-0.2.1 → memware-0.2.2}/integrations/hermes/upstream/plugins/memory/memware/__init__.py
RENAMED
|
File without changes
|
{memware-0.2.1 → memware-0.2.2}/integrations/hermes/upstream/plugins/memory/memware/config_schema.py
RENAMED
|
File without changes
|
{memware-0.2.1 → memware-0.2.2}/integrations/hermes/upstream/plugins/memory/memware/plugin.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|