agmem 0.1.3__py3-none-any.whl → 0.1.4__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agmem
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Agentic Memory Version Control System - Git for AI agent memories
5
5
  Home-page: https://github.com/vivek-tiwari-vt/agmem
6
6
  Author: agmem Team
@@ -33,6 +33,7 @@ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
33
33
  Requires-Dist: black==24.10.0; extra == "dev"
34
34
  Requires-Dist: flake8>=5.0.0; extra == "dev"
35
35
  Requires-Dist: mypy>=1.0.0; extra == "dev"
36
+ Requires-Dist: bandit[toml]>=1.7.0; extra == "dev"
36
37
  Provides-Extra: llm
37
38
  Requires-Dist: openai>=1.0.0; extra == "llm"
38
39
  Requires-Dist: anthropic>=0.18.0; extra == "llm"
@@ -68,6 +69,8 @@ Provides-Extra: crypto
68
69
  Requires-Dist: cryptography>=41.0.0; extra == "crypto"
69
70
  Provides-Extra: ipfs
70
71
  Requires-Dist: requests>=2.28.0; extra == "ipfs"
72
+ Provides-Extra: ipfs-daemon
73
+ Requires-Dist: ipfshttpclient>=0.8.0; extra == "ipfs-daemon"
71
74
  Provides-Extra: all
72
75
  Requires-Dist: mcp>=1.0.0; extra == "all"
73
76
  Requires-Dist: cryptography>=41.0.0; extra == "all"
@@ -129,12 +132,12 @@ agmem solves all of these problems with a familiar Git-like interface.
129
132
  - ✅ **Tamper-evident audit trail** — Append-only hash-chained log (init, add, commit, checkout, merge, push, pull, config); `agmem audit` and `agmem audit --verify`
130
133
  - ✅ **Multi-agent trust** — Trust store (full / conditional / untrusted) per public key; applied on pull/merge; clone copies remote keys
131
134
  - ✅ **Conflict resolution** — `agmem resolve` with ours/theirs/both; conflicts persisted in `.mem/merge/`; path-safe
132
- - ✅ **Differential privacy** — Epsilon/delta budget in `.mem/privacy_budget.json`; `--private` on `agmem distill` and `agmem garden` when enabled
133
- - ✅ **Pack files & GC** — `agmem gc` (reachable from refs, prune loose, optional repack); pack format and index in core
135
+ - ✅ **Differential privacy** — Epsilon/delta budget in `.mem/privacy_budget.json`; `--private` on `agmem distill` and `agmem garden`; noise applied to counts and frontmatter
136
+ - ✅ **Pack files & GC** — `agmem gc [--repack]` (reachable from refs, prune loose, optional pack file + index); ObjectStore reads from pack when loose missing
134
137
  - ✅ **Multi-provider LLM** — OpenAI and Anthropic via `memvcs.core.llm`; config/repo or env; used by gardener, distiller, consistency, merge
135
138
  - ✅ **Temporal querying** — Point-in-time and range queries in temporal index; frontmatter timestamps
136
- - ✅ **Federated collaboration** — `agmem federated push|pull` (stub) for coordinator-based summary sharing
137
- - ✅ **Zero-knowledge proofs** — `agmem prove` (stub) for keyword containment and memory freshness
139
+ - ✅ **Federated collaboration** — `agmem federated push|pull`; real summaries (topic counts, fact hashes); optional DP on outbound; coordinator API in docs/FEDERATED.md
140
+ - ✅ **Zero-knowledge proofs** — `agmem prove` (hash/signature-based): keyword containment (Merkle set membership), memory freshness (signed timestamp)
138
141
  - ✅ **Daemon health** — Periodic Merkle verification in daemon loop; safe auto-remediation hooks
139
142
  - ✅ **GPU acceleration** — Vector store detects GPU for embedding model when available
140
143
  - ✅ **Optional** — `serve`, `daemon` (watch + auto-commit), `garden` (episode archival), MCP server; install extras as needed
@@ -264,9 +267,9 @@ All commands are listed below. Highlights: **`agmem blame <file>`** (who changed
264
267
  | `agmem verify [ref]` | Belief consistency (contradictions); use `--crypto` to verify commit Merkle/signature |
265
268
  | `agmem audit [--verify] [--max n]` | Show tamper-evident audit log; `--verify` checks hash chain |
266
269
  | `agmem resolve [path]` | Resolve merge conflicts (ours/theirs/both); path under `current/` |
267
- | `agmem gc [--dry-run] [--prune-days n]` | Garbage collection: delete unreachable loose objects; optional repack |
268
- | `agmem prove --memory <path> --property keyword\|freshness --value <v> [-o out]` | Generate ZK proofs (stub) |
269
- | `agmem federated push\|pull` | Federated collaboration (stub; requires coordinator in config) |
270
+ | `agmem gc [--dry-run] [--repack] [--prune-days n]` | Garbage collection: delete unreachable loose objects; optional pack file creation |
271
+ | `agmem prove --memory <path> --property keyword\|freshness --value <v> [-o out]` | Generate ZK proofs (keyword: Merkle set membership; freshness: signed timestamp) |
272
+ | `agmem federated push\|pull` | Federated collaboration (real summaries, optional DP; requires coordinator in config) |
270
273
 
271
274
  ### Optional (install extras)
272
275
 
@@ -370,26 +373,26 @@ The following 18 capabilities are implemented (or stubbed) per the agmem feature
370
373
 
371
374
  | # | Feature | Description |
372
375
  |---|---------|-------------|
373
- | **9** | **Decentralized storage (IPFS)** | Push/pull via IPFS CIDs; pinning and gateway fallback. Stub in `memvcs.core.ipfs_remote`; optional dependency. |
374
- | **10** | **Pack files and garbage collection** | Pack loose objects into pack file + index; GC deletes unreachable objects. **Command:** `agmem gc [--dry-run] [--prune-days n]`. Config: `gc_prune_days` (default 90). |
375
- | **11** | **Enhanced cloud remote operations** | Push conflict detection: non–fast-forward push rejected with a clear message. S3/GCS remotes and distributed locking in storage layer. |
376
+ | **9** | **Decentralized storage (IPFS)** | Push/pull via gateway (POST /api/v0/add, GET /ipfs/<cid>). Bundle/unbundle in `memvcs.core.ipfs_remote`; optional `agmem[ipfs]`. |
377
+ | **10** | **Pack files and garbage collection** | Pack loose objects into pack file + index; GC deletes unreachable; ObjectStore reads from pack. **Command:** `agmem gc [--dry-run] [--repack] [--prune-days n]`. |
378
+ | **11** | **Enhanced cloud remote operations** | Push conflict detection; S3/GCS remotes with distributed locking (acquire before push/fetch, release in finally). Config: `lock_table` for S3. |
376
379
 
377
380
  ### Tier 5 — Intelligence and retrieval
378
381
 
379
382
  | # | Feature | Description |
380
383
  |---|---------|-------------|
381
384
  | **12** | **Multi-provider LLM** | `memvcs.core.llm`: OpenAI and Anthropic; factory by config or env. Used by gardener, distiller, consistency checker, merge. Credentials via env (e.g. `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`). |
382
- | **13** | **Enhanced semantic compression** | Multi-stage pipeline (chunk fact extraction dedup embed → tiered storage); hybrid retrieval. Docstrings and design in distiller/vector store. |
383
- | **14** | **Temporal querying and time-travel** | Point-in-time and range queries in `memvcs.core.temporal_index`; frontmatter timestamps; “state at T” resolution. |
384
- | **15** | **Cross-memory relationship graph** | Knowledge graph extended with co-occurrence, semantic similarity, causal and entity edges; incremental updates. Docstrings in `knowledge_graph.py`. |
385
+ | **13** | **Enhanced semantic compression** | Multi-stage pipeline in `memvcs.core.compression_pipeline`: chunk, fact extraction, dedup by hash; hybrid retrieval in strategies. |
386
+ | **14** | **Temporal querying and time-travel** | Point-in-time and range in `memvcs.core.temporal_index`; CLI: `agmem when --from/--to`, `agmem timeline --from/--to`. |
387
+ | **15** | **Cross-memory relationship graph** | Knowledge graph: co-occurrence, causal edges; incremental-update docstring in `knowledge_graph.py`. |
385
388
 
386
389
  ### Tier 6 — Operations and maintenance
387
390
 
388
391
  | # | Feature | Description |
389
392
  |---|---------|-------------|
390
- | **16** | **Automated memory health monitoring** | Daemon runs periodic Merkle verification; safe auto-remediation hooks; unsafe actions alert only. |
391
- | **17** | **GPU-accelerated operations** | Vector store detects GPU for embedding model (e.g. sentence-transformers with CUDA/Metal); transparent CPU fallback. |
392
- | **18** | **Test suite and quality** | Broad tests: object store, merge, crypto (Merkle, proofs, verify), trust, privacy budget, pack/GC, resolve helpers, encryption, LLM provider; CI with coverage. |
393
+ | **16** | **Automated memory health monitoring** | Daemon: configurable `daemon.health_check_interval_seconds` and `AGMEM_DAEMON_HEALTH_INTERVAL`; alert only on verify failure; suggest `agmem fsck`. |
394
+ | **17** | **GPU-accelerated operations** | Vector store `_device()` returns cuda/mps/cpu; model loaded with that device. |
395
+ | **18** | **Test suite and quality** | Tests: crypto (tampered blob, key missing), encryption (wrong key, corrupted ciphertext), privacy budget, pack/GC, ZK prove/verify, federated mock, IPFS bundle; see docs/TEST_REPORT.md. |
393
396
 
394
397
  ### New files and config (summary)
395
398
 
@@ -399,7 +402,10 @@ The following 18 capabilities are implemented (or stubbed) per the agmem feature
399
402
  | `memvcs/core/audit.py` | Tamper-evident audit append and verify |
400
403
  | `memvcs/core/trust.py` | Trust store (key → level) |
401
404
  | `memvcs/core/privacy_budget.py` | Epsilon/delta budget for DP |
402
- | `memvcs/core/pack.py` | Pack format, index, GC |
405
+ | `memvcs/core/pack.py` | Pack format, index, GC, repack |
406
+ | `memvcs/core/compression_pipeline.py` | Chunk, fact extraction, dedup; hybrid retrieval |
407
+ | `memvcs/core/zk_proofs.py` | Hash/signature-based proofs (keyword, freshness) |
408
+ | `docs/FEDERATED.md` | Coordinator API for federated push/pull |
403
409
  | `memvcs/core/encryption.py` | AES-256-GCM, Argon2id, config |
404
410
  | `memvcs/core/llm/` | LLM provider interface and OpenAI/Anthropic |
405
411
  | `memvcs/core/zk_proofs.py` | ZK proof stubs |
@@ -1,4 +1,4 @@
1
- agmem-0.1.3.dist-info/licenses/LICENSE,sha256=X_S6RBErW-F0IDbM3FAEoDB-zxExFnl2m8640rTXphM,1067
1
+ agmem-0.1.4.dist-info/licenses/LICENSE,sha256=X_S6RBErW-F0IDbM3FAEoDB-zxExFnl2m8640rTXphM,1067
2
2
  memvcs/__init__.py,sha256=mXwHTSlUPWo4ERqJLGJnxmxtGQQHPSbXb4IpO61l04M,193
3
3
  memvcs/cli.py,sha256=YF06oMNjKWUmiNahILmfjrIXgoXzU-5BJFmbunSb8Sc,6075
4
4
  memvcs/commands/__init__.py,sha256=A2D6xWaO6epU7iV4QSvqvF5TspnwRyDN7NojmGatPrE,510
@@ -11,21 +11,21 @@ memvcs/commands/checkout.py,sha256=xaYZSbCQ-MyLWPtwA2FdH6WqGMI3oF3R2JmCufGBVFg,3
11
11
  memvcs/commands/clean.py,sha256=e0OhSQdHfFnOPTRbyKbM8IcX4yJD5n_kaBKjIeoaRBo,1973
12
12
  memvcs/commands/clone.py,sha256=aB0LcugIWJE9IEez6y70KlpZu4eIF2EdXZxE24jXyac,3260
13
13
  memvcs/commands/commit.py,sha256=W4ulVZuEETJh1SHpscaQfNjyQMqeIE0AYZIbMbTrsq4,6801
14
- memvcs/commands/daemon.py,sha256=dD20IPjwebtmMCO8vE5hVQoTDzLomxPiCoDwkrST7OU,9895
14
+ memvcs/commands/daemon.py,sha256=KM9XSCdm4-aVBi4flKjiefpG8SfSrYl10phoMpz0gyk,10707
15
15
  memvcs/commands/decay.py,sha256=QcgOTMJZxrfw_AOz94YHA3LGoNXRMDn69TxWlUrpSw4,2421
16
16
  memvcs/commands/diff.py,sha256=KcgD57_fae4uvQ8G9ZbXmLpAYYIDiWiBuVcjsDtyE1U,5480
17
- memvcs/commands/distill.py,sha256=YbPU1_os8GBFMjznNoOsevdqJa_MtiGO8B5vFxIjE9g,2858
18
- memvcs/commands/federated.py,sha256=D4UiRFZWnQrW7kM4KqY0s2ttAqM2t-xeJN5-b_WKSHE,1633
17
+ memvcs/commands/distill.py,sha256=reOldqg0lMgqIlpYEIKYfN_TxNwsjU9RnI8Uah1VqTQ,3088
18
+ memvcs/commands/federated.py,sha256=Zj4kxHnjdIs1xu4v7B8XosQXNYK8Alv4I0kJQpmJe6Y,1840
19
19
  memvcs/commands/fsck.py,sha256=AdJBMLA2myQ0cJJcjUgsYptsE3qvX4JQc9UAwVmSHlA,7772
20
- memvcs/commands/garden.py,sha256=vSmI6XPpxK3EkLHCVpjWRg7eJPszHHzbcH8B14csueI,3820
21
- memvcs/commands/gc.py,sha256=fHQl99HckZ8CcF2iI-PSH46aN-RmbPk6cRrC0WEE_94,1360
20
+ memvcs/commands/garden.py,sha256=8JiLe3JRkOhY-N-h-IDuvdJiECiSElnUzXVtxtU2QgY,4050
21
+ memvcs/commands/gc.py,sha256=vLGREkcHjR_rDvTvEh-dwNkAeTB9y4fU-BwBGbXOEg4,1940
22
22
  memvcs/commands/graph.py,sha256=MDi6bK2w0OrpK5VOE8XXw5gQX7BuD7VzUyqJ5Ra9Bsg,4746
23
23
  memvcs/commands/init.py,sha256=TsrLFLXwkDFT0opsYJTfwu0NIxLrNiiba5SpzRtxjDI,1614
24
24
  memvcs/commands/log.py,sha256=eNlLs0-PS2nF0pMAMI8izKGUiEb2m3S0RB4Zh6cUQpE,2859
25
25
  memvcs/commands/mcp.py,sha256=PMfwVD6uHltN58Jh7IOiS1w7oND42tg14QKRCJNudmY,1740
26
26
  memvcs/commands/merge.py,sha256=s3QLZp-_I6OvhllLhL9yFZAQ8d4M4FbvxkXV7gUgw5M,4877
27
27
  memvcs/commands/pack.py,sha256=rIDjMpxJG0oxrWnB3vCGHqviCITIeIbdy3nhuHVHzM8,3629
28
- memvcs/commands/prove.py,sha256=o_RPvSK061WiaM4YfNIGlTC357s5_AfY4iOx4-hwFyE,2094
28
+ memvcs/commands/prove.py,sha256=qQYYV5GdLd0Av4pwaxNvUCcl5pmiBwCrlXJwRtXVCF4,2141
29
29
  memvcs/commands/pull.py,sha256=hn9FIlNc3KUr5EUDo4_66KQSK0BSSXjOn32xaDNxf0Q,3621
30
30
  memvcs/commands/push.py,sha256=0abEdHkCMfHpH_Nmlw3OaU7Hzi0-RXF-cTVHpiSPw6k,5086
31
31
  memvcs/commands/recall.py,sha256=7nwC4mFYpdjKWG-Cs3cpDLr5_SgYJ6HkVSXDOkFke5A,4592
@@ -42,33 +42,34 @@ memvcs/commands/stash.py,sha256=CD3mRWehcmfVRPGGpndUBdTT_ku4LC_rmSKPvTEOTAo,3193
42
42
  memvcs/commands/status.py,sha256=O6BgzTiW3UHjXx6OKwH8X4g0hP0IlYDgr7As5RmeujU,3447
43
43
  memvcs/commands/tag.py,sha256=CaCnA3JifVrdr8DfX4g0bp-_oRvagJkQFcI4bJbW1uM,3004
44
44
  memvcs/commands/test.py,sha256=HZrpGZQhu9HnGZLjiq8TXi8jfOZqP-wc3bW6mgpP2yk,3926
45
- memvcs/commands/timeline.py,sha256=xdOr2jz-_ArSPY-GxwXBloiwhfBzIfz4MAi-JEhP8H0,3666
45
+ memvcs/commands/timeline.py,sha256=hH4kqd0cHbdtnjMrr_Sw6lt0kmu0yEVctHGOQ2iYK5s,4763
46
46
  memvcs/commands/tree.py,sha256=vdULq4vIXA_4gNfMnHn_Y78BwE0sJoeTBOnFJR3WsZ4,4927
47
47
  memvcs/commands/verify.py,sha256=04CVW5NYWkUlPJ5z1Kci6dfQFM6UmPTGZh9ZextFLMc,3887
48
- memvcs/commands/when.py,sha256=MMQ15PFXFCTmjIq7dr0tC0XvGAdndMvckVnnWehc60Y,3692
48
+ memvcs/commands/when.py,sha256=gbSQHk96zu4TiH1QIdQJUeSsy9WFbjaheh5jjTsGopw,4772
49
49
  memvcs/core/__init__.py,sha256=dkIC-4tS0GhwV2mZIbofEe8xR8uiFwrxslGf1aXwhYg,493
50
50
  memvcs/core/access_index.py,sha256=HhacnzSUASzRV2jhDHkwRFoPS3rtqh9n9yE1VV7JXpk,5596
51
51
  memvcs/core/audit.py,sha256=8APkm9Spl_-1rIdyRQz1elyxOeK3nlpwm0CLkpLlhTE,3732
52
+ memvcs/core/compression_pipeline.py,sha256=ejFXBTHfBYbCD86a5V0-0wA39K-SBG7dt09oAy-XP5s,5481
52
53
  memvcs/core/config_loader.py,sha256=j-jgLDp2TRzWN9ZEZebfWSfatevBNYs0FEb3ud1SIR8,8277
53
54
  memvcs/core/consistency.py,sha256=YOG8xhqZLKZCLbai2rdcP0KxYPNGFv5RRMwrQ6qCeyc,7462
54
55
  memvcs/core/constants.py,sha256=WUjAb50BFcF0mbFi_GNteDLCxLihmViBm9Fb-JMPmbM,220
55
- memvcs/core/crypto_verify.py,sha256=WrOgDtIEwpjcPfDj2uqwdyg3sIM3j1hUSVPEFvptWnc,9936
56
+ memvcs/core/crypto_verify.py,sha256=-yphuOE4bP-V1_bpMfNnJTLtpAdtKq8OV2hNUlUxiwk,10432
56
57
  memvcs/core/decay.py,sha256=ROGwnqngs7eJNkbKmwyOdij607m73vpmoJqzrIDLBzk,6581
57
58
  memvcs/core/diff.py,sha256=koEHTLciIUxYKVJVuvmY0GDXMgDgGZP_qg5RayhF-iE,13226
58
- memvcs/core/distiller.py,sha256=fsZqOHxHTro0yy7rrMW3j5oUMibsVOShlikn2UjOieQ,10440
59
+ memvcs/core/distiller.py,sha256=859NUR3gzYQuvDFxMtGB2NcTGRmRj4VJyOZTlDKvSzI,11683
59
60
  memvcs/core/encryption.py,sha256=epny_nlW6ylllv1qxs1mAcFq-PrLIisgfot4llOoAqw,5289
60
- memvcs/core/federated.py,sha256=H9U5-TgiY_OjQ6Adn85Pw6hV1FYtlXgjjQYMTor35EY,2828
61
- memvcs/core/gardener.py,sha256=_io40S0OQzPz3euKpS0M6QqEApxxkaX1UXs8uYttQag,16193
61
+ memvcs/core/federated.py,sha256=RRNzhDVahTM-XQanT__8IBfGsS6fPDbq40b4v327iHg,5374
62
+ memvcs/core/gardener.py,sha256=YKw4amhlPrX34gvg71PNUWmERUhrqvhrCuHnOj229gs,17462
62
63
  memvcs/core/hooks.py,sha256=XF9z8J5sWjAcuOyWQ2nuvEzK0UV8s4ThrcltaBZttzw,5448
63
- memvcs/core/ipfs_remote.py,sha256=985xAtSw4P9yxxw-WXtQvNSY8b2hOpFp9TNBbHpIDBk,1062
64
- memvcs/core/knowledge_graph.py,sha256=sa2jw9AwJIaKs8obHHXdnExxHx-CebtVrgjJaa2qo14,12939
64
+ memvcs/core/ipfs_remote.py,sha256=1Xob0Tiz0-GevgQrBhwUBifnVLO8U0dUIlvJS88BMBk,6651
65
+ memvcs/core/knowledge_graph.py,sha256=6UuSdkaaXQnVti9TK10ak_KeCn8apLOgB70GXN_1I-Q,16370
65
66
  memvcs/core/merge.py,sha256=x2eSaxr4f63Eq00FCJ6DDe2TZU8H5yHQpzKzMhYsaFw,19871
66
- memvcs/core/objects.py,sha256=I4UlqQoBPuzyPHKWboZbZP6cy5hL1Dz18IfBm6lInNk,10455
67
- memvcs/core/pack.py,sha256=zXwsRWSzfup6KrkxX6_jIw6gwxmRbmEnFCoVvhNZXHQ,2999
67
+ memvcs/core/objects.py,sha256=G6EigwJI0c9NZ9LB36L-3beNYt_MwETNgbtwnrptqMA,11004
68
+ memvcs/core/pack.py,sha256=SiEReq9EMzffd3trnc38REWrh5Vo5HAmErovNgsx01U,9749
68
69
  memvcs/core/pii_scanner.py,sha256=T6gQ1APFrSDk980fjnv4ZMF-UztbJgmUFSwGrwWixEw,10802
69
70
  memvcs/core/privacy_budget.py,sha256=fOPlxoKEAmsKtda-OJCrSaKjTyw7ekcqdN7KfRBw1CY,2113
70
71
  memvcs/core/refs.py,sha256=4Nx2ZVRa_DzfUZ4O1AwzOHEjoGAEICJKqSd9GxaiD_g,16754
71
- memvcs/core/remote.py,sha256=a-Nwr7m4fUK86K-dxG1CawMID7JX2MZ6V94RHkGjkeI,11625
72
+ memvcs/core/remote.py,sha256=HmGXx-NZFw7wgf0rHcwmGOQSWUoHNP85RHP5UaUDuuE,19429
72
73
  memvcs/core/repository.py,sha256=NzC2UFPv6ePxi5lfiSKyZFLclH4bJpWJz88pY7tDiv4,20605
73
74
  memvcs/core/schema.py,sha256=_CrEWCdArc0yDJ04GT7fyvjHqkal7gegdFSsFOjVpBc,15287
74
75
  memvcs/core/staging.py,sha256=dptdGi_74lhDkcGqGVU39ZyTkb25j-Rnkz0GWi83W1k,7221
@@ -76,7 +77,7 @@ memvcs/core/temporal_index.py,sha256=81hZHlVElp2UpXjseFVCdDUwxGM45zIU-y1dDlOhFHI
76
77
  memvcs/core/test_runner.py,sha256=7-0jCvji63JRbVfy3LNQWIQ7VL5weulOoG7SY1-YJbw,11496
77
78
  memvcs/core/trust.py,sha256=msx80Cl3bxyQTY8mFUKWY9P6l3zb1s8FafympgHwtpo,3494
78
79
  memvcs/core/vector_store.py,sha256=yUAp5BlaAtjkrtsdY1I-vmAp_YIFgJykBoNlp5hcg0I,11063
79
- memvcs/core/zk_proofs.py,sha256=xo0uB6sbufz9OcnZLJK2iH3CXtuVAMopGNp0S1ne_Wo,967
80
+ memvcs/core/zk_proofs.py,sha256=dnwMqhGtzDQtaNuO1bhuLchqYyEDnXzbtd-jQH_M0qQ,5512
80
81
  memvcs/core/llm/__init__.py,sha256=vnjtE9Xlv9a2pZV88DMT9JaINkZ30hC9VLPL5lJRlps,236
81
82
  memvcs/core/llm/anthropic_provider.py,sha256=O1eaCb9r464ajLJz-Gy8lGxBie5ojRUZ_5HdgRXO5KY,1540
82
83
  memvcs/core/llm/base.py,sha256=qPzg3KPAMeoyWGc_2JoVR4-plpdft5Rc2g9uO-Z4fJQ,623
@@ -98,8 +99,8 @@ memvcs/retrieval/recaller.py,sha256=8KY-XjMUz5_vcKf46zI64uk1DEM__u7wM92ShukOtsY,
98
99
  memvcs/retrieval/strategies.py,sha256=26yxQQubQfjxWQXknfVMxuzPHf2EcZxJg_B99BEdl5c,11458
99
100
  memvcs/utils/__init__.py,sha256=8psUzz4Ntv2GzbRebkeVsoyC6Ck-FIwi0_lfYdj5oho,185
100
101
  memvcs/utils/helpers.py,sha256=37zg_DcQ2y99b9NSLqxFkglHe13rJXKhFDpEbQ7iLhM,4121
101
- agmem-0.1.3.dist-info/METADATA,sha256=gtN5tU3wSR8trf5kKJIBP6KgAGQrjm66dCs8NRvu_ao,36834
102
- agmem-0.1.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
103
- agmem-0.1.3.dist-info/entry_points.txt,sha256=at7eWycgjqOo1wbUMECnXUsNo3gpCkJTU71OzrGLHu0,42
104
- agmem-0.1.3.dist-info/top_level.txt,sha256=HtMMsKuwLKLOdgF1GxqQztqFM54tTJctVdJuOec6B-4,7
105
- agmem-0.1.3.dist-info/RECORD,,
102
+ agmem-0.1.4.dist-info/METADATA,sha256=IU1QZw4zdsUApbsgLUvoh8BNBtbOc3AeSUINr1GSx80,37487
103
+ agmem-0.1.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
104
+ agmem-0.1.4.dist-info/entry_points.txt,sha256=at7eWycgjqOo1wbUMECnXUsNo3gpCkJTU71OzrGLHu0,42
105
+ agmem-0.1.4.dist-info/top_level.txt,sha256=HtMMsKuwLKLOdgF1GxqQztqFM54tTJctVdJuOec6B-4,7
106
+ agmem-0.1.4.dist-info/RECORD,,
memvcs/commands/daemon.py CHANGED
@@ -205,13 +205,28 @@ class DaemonCommand:
205
205
 
206
206
  # Health monitoring: periodic integrity check (configurable interval)
207
207
  last_health_check = 0
208
- health_check_interval = 3600 # 1 hour
208
+ health_check_interval = 3600 # default 1 hour
209
+ try:
210
+ from ..core.config_loader import load_agmem_config
211
+ config = load_agmem_config(repo.root)
212
+ daemon_cfg = config.get("daemon") or {}
213
+ health_check_interval = int(daemon_cfg.get("health_check_interval_seconds", 3600))
214
+ if health_check_interval <= 0:
215
+ health_check_interval = 0
216
+ except Exception:
217
+ pass
218
+ env_interval = os.environ.get("AGMEM_DAEMON_HEALTH_INTERVAL")
219
+ if env_interval is not None:
220
+ try:
221
+ health_check_interval = int(env_interval)
222
+ except ValueError:
223
+ pass
209
224
 
210
225
  try:
211
226
  while running:
212
227
  time.sleep(1)
213
228
 
214
- # Periodic health check (Merkle/signature, optional)
229
+ # Periodic health check (Merkle/signature, optional). Alert only; no destructive action.
215
230
  if (
216
231
  health_check_interval
217
232
  and (time.time() - last_health_check) >= health_check_interval
@@ -229,8 +244,10 @@ class DaemonCommand:
229
244
  load_public_key(repo.mem_dir),
230
245
  mem_dir=repo.mem_dir,
231
246
  )
232
- if not ok and err and "tampered" in (err or "").lower():
247
+ if not ok and err:
233
248
  sys.stderr.write(f"Health check: {err}\n")
249
+ if "tampered" in (err or "").lower():
250
+ sys.stderr.write("Run 'agmem fsck' for safe integrity check.\n")
234
251
  except Exception:
235
252
  pass
236
253
  last_health_check = time.time()
@@ -53,21 +53,29 @@ class DistillCommand:
53
53
  if code != 0:
54
54
  return code
55
55
 
56
- if getattr(args, "private", False):
56
+ use_dp = getattr(args, "private", False)
57
+ dp_epsilon = None
58
+ dp_delta = None
59
+ if use_dp:
57
60
  from ..core.privacy_budget import load_budget, spend_epsilon
58
61
 
59
- spent, max_eps, _ = load_budget(repo.mem_dir)
62
+ spent, max_eps, delta = load_budget(repo.mem_dir)
60
63
  epsilon_cost = 0.1
61
64
  if not spend_epsilon(repo.mem_dir, epsilon_cost):
62
65
  print(f"Privacy budget exceeded (spent {spent:.2f}, max {max_eps}).")
63
66
  return 1
64
67
  if spent + epsilon_cost > max_eps * 0.8:
65
68
  print(f"Privacy budget low: {spent + epsilon_cost:.2f}/{max_eps}")
69
+ dp_epsilon = 0.05
70
+ dp_delta = delta
66
71
 
67
72
  config = DistillerConfig(
68
73
  source_dir=args.source,
69
74
  target_dir=args.target,
70
75
  create_safety_branch=not args.no_branch,
76
+ use_dp=use_dp,
77
+ dp_epsilon=dp_epsilon,
78
+ dp_delta=dp_delta,
71
79
  )
72
80
  distiller = Distiller(repo, config)
73
81
 
@@ -38,7 +38,13 @@ class FederatedCommand:
38
38
  return 1
39
39
 
40
40
  if args.action == "push":
41
- summary = produce_local_summary(repo.root, cfg["memory_types"])
41
+ summary = produce_local_summary(
42
+ repo.root,
43
+ cfg["memory_types"],
44
+ use_dp=cfg.get("use_dp", False),
45
+ dp_epsilon=cfg.get("dp_epsilon") or 0.1,
46
+ dp_delta=cfg.get("dp_delta") or 1e-5,
47
+ )
42
48
  msg = push_updates(repo.root, summary)
43
49
  print(msg)
44
50
  return 0 if "Pushed" in msg else 1
memvcs/commands/garden.py CHANGED
@@ -50,14 +50,19 @@ class GardenCommand:
50
50
  if code != 0:
51
51
  return code
52
52
 
53
- if getattr(args, "private", False):
53
+ use_dp = getattr(args, "private", False)
54
+ dp_epsilon = None
55
+ dp_delta = None
56
+ if use_dp:
54
57
  from ..core.privacy_budget import load_budget, spend_epsilon
55
58
 
56
- spent, max_eps, _ = load_budget(repo.mem_dir)
59
+ spent, max_eps, delta = load_budget(repo.mem_dir)
57
60
  epsilon_cost = 0.1
58
61
  if not spend_epsilon(repo.mem_dir, epsilon_cost):
59
62
  print(f"Privacy budget exceeded (spent {spent:.2f}, max {max_eps}).")
60
63
  return 1
64
+ dp_epsilon = 0.05
65
+ dp_delta = delta
61
66
 
62
67
  # Build config
63
68
  config = GardenerConfig(
@@ -65,6 +70,9 @@ class GardenCommand:
65
70
  auto_commit=not args.no_commit,
66
71
  llm_provider=args.llm if args.llm != "none" else None,
67
72
  llm_model=args.model,
73
+ use_dp=use_dp,
74
+ dp_epsilon=dp_epsilon,
75
+ dp_delta=dp_delta,
68
76
  )
69
77
 
70
78
  # Create gardener
memvcs/commands/gc.py CHANGED
@@ -7,7 +7,7 @@ Remove unreachable objects; optionally repack.
7
7
  import argparse
8
8
 
9
9
  from ..commands.base import require_repo
10
- from ..core.pack import run_gc
10
+ from ..core.pack import run_gc, run_repack
11
11
 
12
12
 
13
13
  class GcCommand:
@@ -30,6 +30,11 @@ class GcCommand:
30
30
  metavar="N",
31
31
  help="Consider reflog entries within N days (default 90)",
32
32
  )
33
+ parser.add_argument(
34
+ "--repack",
35
+ action="store_true",
36
+ help="After GC, pack reachable loose objects into a pack file",
37
+ )
33
38
 
34
39
  @staticmethod
35
40
  def execute(args) -> int:
@@ -48,4 +53,14 @@ class GcCommand:
48
53
  print(f"Would remove {deleted} unreachable object(s) ({freed} bytes).")
49
54
  else:
50
55
  print(f"Removed {deleted} unreachable object(s) ({freed} bytes reclaimed).")
56
+
57
+ if getattr(args, "repack", False) and not args.dry_run:
58
+ packed, repack_freed = run_repack(
59
+ repo.mem_dir,
60
+ repo.object_store,
61
+ gc_prune_days=gc_prune_days,
62
+ dry_run=False,
63
+ )
64
+ if packed > 0:
65
+ print(f"Packed {packed} object(s) into pack file ({repack_freed} bytes from loose).")
51
66
  return 0
memvcs/commands/prove.py CHANGED
@@ -57,10 +57,10 @@ class ProveCommand:
57
57
  if not args.value:
58
58
  print("--value required for freshness (ISO date)")
59
59
  return 1
60
- ok = prove_memory_freshness(path, args.value, out_path)
60
+ ok = prove_memory_freshness(path, args.value, out_path, mem_dir=repo.mem_dir)
61
61
 
62
62
  if not ok:
63
- print("Proof generation not yet implemented (zk backend required).")
63
+ print("Proof generation failed (keyword not in file, or signing key not set for freshness).")
64
64
  return 1
65
65
  print(f"Proof written to {out_path}")
66
66
  return 0
@@ -28,6 +28,18 @@ class TimelineCommand:
28
28
  default=20,
29
29
  help="Max commits to show (default: 20)",
30
30
  )
31
+ parser.add_argument(
32
+ "--from",
33
+ dest="from_ts",
34
+ metavar="ISO",
35
+ help="Start of time range (ISO 8601, e.g. 2025-01-01)",
36
+ )
37
+ parser.add_argument(
38
+ "--to",
39
+ dest="to_ts",
40
+ metavar="ISO",
41
+ help="End of time range (ISO 8601)",
42
+ )
31
43
 
32
44
  @staticmethod
33
45
  def execute(args) -> int:
@@ -36,6 +48,17 @@ class TimelineCommand:
36
48
  return code
37
49
 
38
50
  filepath = args.file.replace("current/", "").lstrip("/")
51
+ from_ts = getattr(args, "from_ts", None)
52
+ to_ts = getattr(args, "to_ts", None)
53
+ commits_in_range = None
54
+ if from_ts and to_ts:
55
+ try:
56
+ from ..core.temporal_index import TemporalIndex
57
+ ti = TemporalIndex(repo.mem_dir, repo.object_store)
58
+ range_entries = ti.range_query(from_ts, to_ts)
59
+ commits_in_range = {ch for _, ch in range_entries}
60
+ except Exception:
61
+ pass
39
62
 
40
63
  # Walk commit history
41
64
  head = repo.refs.get_head()
@@ -51,6 +74,10 @@ class TimelineCommand:
51
74
  if commit_hash in seen:
52
75
  break
53
76
  seen.add(commit_hash)
77
+ if commits_in_range is not None and commit_hash not in commits_in_range:
78
+ commit = Commit.load(repo.object_store, commit_hash)
79
+ commit_hash = commit.parents[0] if commit and commit.parents else None
80
+ continue
54
81
 
55
82
  commit = Commit.load(repo.object_store, commit_hash)
56
83
  if not commit:
memvcs/commands/when.py CHANGED
@@ -34,6 +34,18 @@ class WhenCommand:
34
34
  default=10,
35
35
  help="Max commits to report (default: 10)",
36
36
  )
37
+ parser.add_argument(
38
+ "--from",
39
+ dest="from_ts",
40
+ metavar="ISO",
41
+ help="Start of time range (ISO 8601)",
42
+ )
43
+ parser.add_argument(
44
+ "--to",
45
+ dest="to_ts",
46
+ metavar="ISO",
47
+ help="End of time range (ISO 8601)",
48
+ )
37
49
 
38
50
  @staticmethod
39
51
  def execute(args) -> int:
@@ -48,6 +60,17 @@ class WhenCommand:
48
60
 
49
61
  fact_lower = args.fact.lower()
50
62
  file_filter = args.file.replace("current/", "").lstrip("/") if args.file else None
63
+ from_ts = getattr(args, "from_ts", None)
64
+ to_ts = getattr(args, "to_ts", None)
65
+ commits_in_range = None
66
+ if from_ts and to_ts:
67
+ try:
68
+ from ..core.temporal_index import TemporalIndex
69
+ ti = TemporalIndex(repo.mem_dir, repo.object_store)
70
+ range_entries = ti.range_query(from_ts, to_ts)
71
+ commits_in_range = {ch for _, ch in range_entries}
72
+ except Exception:
73
+ pass
51
74
 
52
75
  # Walk commit history from HEAD
53
76
  head = repo.refs.get_head()
@@ -63,6 +86,10 @@ class WhenCommand:
63
86
  if commit_hash in seen:
64
87
  break
65
88
  seen.add(commit_hash)
89
+ if commits_in_range is not None and commit_hash not in commits_in_range:
90
+ commit = Commit.load(repo.object_store, commit_hash)
91
+ commit_hash = commit.parents[0] if commit and commit.parents else None
92
+ continue
66
93
 
67
94
  commit = Commit.load(repo.object_store, commit_hash)
68
95
  if not commit:
@@ -0,0 +1,157 @@
1
+ """
2
+ Enhanced semantic compression pipeline for agmem (#11).
3
+
4
+ Multi-stage: chunk -> fact extraction -> dedup -> embed -> tiered storage.
5
+ Hybrid retrieval (keyword + vector) is in memvcs.retrieval.strategies.HybridStrategy.
6
+ """
7
+
8
+ import hashlib
9
+ import re
10
+ from pathlib import Path
11
+ from typing import List, Optional, Tuple, Any
12
+
13
+ from .constants import MEMORY_TYPES
14
+
15
+ CHUNK_SIZE_DEFAULT = 512
16
+ CHUNK_OVERLAP = 64
17
+ DEDUP_HASH_ALGO = "sha256"
18
+ TIER_HOT_DAYS = 7
19
+
20
+
21
+ def chunk_by_size(text: str, size: int = CHUNK_SIZE_DEFAULT, overlap: int = CHUNK_OVERLAP) -> List[str]:
22
+ """Split text into chunks by character size with optional overlap."""
23
+ if not text or size <= 0:
24
+ return []
25
+ chunks = []
26
+ start = 0
27
+ while start < len(text):
28
+ end = min(start + size, len(text))
29
+ chunk = text[start:end].strip()
30
+ if chunk:
31
+ chunks.append(chunk)
32
+ start = end - overlap if end < len(text) else len(text)
33
+ return chunks
34
+
35
+
36
+ def chunk_by_sentences(text: str, max_chunk_chars: int = 512) -> List[str]:
37
+ """Split text into chunks by sentence boundaries, up to max_chunk_chars per chunk."""
38
+ if not text:
39
+ return []
40
+ sentences = re.split(r'(?<=[.!?])\s+', text)
41
+ chunks = []
42
+ current = []
43
+ current_len = 0
44
+ for s in sentences:
45
+ s = s.strip()
46
+ if not s:
47
+ continue
48
+ if current_len + len(s) + 1 <= max_chunk_chars:
49
+ current.append(s)
50
+ current_len += len(s) + 1
51
+ else:
52
+ if current:
53
+ chunks.append(" ".join(current))
54
+ current = [s]
55
+ current_len = len(s) + 1
56
+ if current:
57
+ chunks.append(" ".join(current))
58
+ return chunks
59
+
60
+
61
+ def extract_facts_from_chunk(chunk: str) -> List[str]:
62
+ """Extract fact-like lines (bullets or short statements). Reuse distiller logic in callers if needed."""
63
+ facts = []
64
+ for line in chunk.splitlines():
65
+ line = line.strip()
66
+ if not line or line.startswith("#"):
67
+ continue
68
+ if line.startswith("- ") and len(line) > 10:
69
+ facts.append(line)
70
+ elif len(line) > 20 and len(line) < 300 and not line.startswith("```"):
71
+ facts.append(line)
72
+ return facts[:15]
73
+
74
+
75
+ def dedup_by_hash(items: List[str]) -> List[Tuple[str, str]]:
76
+ """Return (item, hash_hex) for unique items by content hash. Order preserved, first occurrence kept."""
77
+ seen_hashes = set()
78
+ result = []
79
+ for item in items:
80
+ h = hashlib.new(DEDUP_HASH_ALGO, item.encode()).hexdigest()
81
+ if h not in seen_hashes:
82
+ seen_hashes.add(h)
83
+ result.append((item, h))
84
+ return result
85
+
86
+
87
+ def dedup_by_similarity_threshold(
88
+ items: List[str], vector_store: Any, threshold: float = 0.95
89
+ ) -> List[str]:
90
+ """Filter items by embedding similarity; keep first of clusters above threshold. Requires vector_store."""
91
+ if not items or vector_store is None:
92
+ return items
93
+ try:
94
+ embeddings = vector_store.embed(items)
95
+ kept = [items[0]]
96
+ for i in range(1, len(items)):
97
+ sims = [vector_store.similarity(embeddings[i], vector_store.embed([kept[j]])[0]) for j in range(len(kept))]
98
+ if not any(s >= threshold for s in sims):
99
+ kept.append(items[i])
100
+ return kept
101
+ except Exception:
102
+ return items
103
+
104
+
105
+ class CompressionPipeline:
106
+ """
107
+ Multi-stage compression: chunk -> optional fact extraction -> dedup -> optional embed -> tiered storage.
108
+ Wire to vector_store and retrieval for hybrid recall.
109
+ """
110
+
111
+ def __init__(
112
+ self,
113
+ chunk_size: int = CHUNK_SIZE_DEFAULT,
114
+ use_sentences: bool = True,
115
+ extract_facts: bool = False,
116
+ dedup_hash: bool = True,
117
+ vector_store: Optional[Any] = None,
118
+ tier_by_recency: bool = True,
119
+ ):
120
+ self.chunk_size = chunk_size
121
+ self.use_sentences = use_sentences
122
+ self.extract_facts = extract_facts
123
+ self.dedup_hash = dedup_hash
124
+ self.vector_store = vector_store
125
+ self.tier_by_recency = tier_by_recency
126
+
127
+ def chunk(self, text: str) -> List[str]:
128
+ """Chunk text by size or sentences."""
129
+ if self.use_sentences:
130
+ return chunk_by_sentences(text, max_chunk_chars=self.chunk_size)
131
+ return chunk_by_size(text, size=self.chunk_size)
132
+
133
+ def run(self, text: str, path: Optional[Path] = None) -> List[Tuple[str, str, Optional[str]]]:
134
+ """
135
+ Run pipeline: chunk -> optional fact extraction -> dedup.
136
+ Returns list of (content, content_hash, tier) where tier is "hot" or "cold" or None.
137
+ """
138
+ chunks = self.chunk(text)
139
+ if self.extract_facts:
140
+ facts = []
141
+ for c in chunks:
142
+ facts.extend(extract_facts_from_chunk(c))
143
+ chunks = facts if facts else chunks
144
+ if self.dedup_hash:
145
+ chunk_tuples = dedup_by_hash(chunks)
146
+ else:
147
+ chunk_tuples = [(c, hashlib.new(DEDUP_HASH_ALGO, c.encode()).hexdigest()) for c in chunks]
148
+ tier = None
149
+ if self.tier_by_recency and path and path.exists():
150
+ try:
151
+ mtime = path.stat().st_mtime
152
+ from datetime import datetime, timezone
153
+ age_days = (datetime.now(timezone.utc).timestamp() - mtime) / 86400
154
+ tier = "hot" if age_days <= TIER_HOT_DAYS else "cold"
155
+ except Exception:
156
+ pass
157
+ return [(c, h, tier) for c, h in chunk_tuples]