agmem 0.1.6__py3-none-any.whl → 0.2.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.
- {agmem-0.1.6.dist-info → agmem-0.2.0.dist-info}/METADATA +12 -6
- {agmem-0.1.6.dist-info → agmem-0.2.0.dist-info}/RECORD +17 -12
- memvcs/commands/daemon.py +37 -1
- memvcs/commands/distill.py +6 -0
- memvcs/coordinator/__init__.py +5 -0
- memvcs/coordinator/server.py +223 -0
- memvcs/core/delta.py +258 -0
- memvcs/core/distiller.py +74 -50
- memvcs/core/pack.py +191 -33
- memvcs/core/remote.py +82 -2
- memvcs/core/zk_proofs.py +62 -5
- memvcs/health/__init__.py +25 -0
- memvcs/health/monitor.py +452 -0
- {agmem-0.1.6.dist-info → agmem-0.2.0.dist-info}/WHEEL +0 -0
- {agmem-0.1.6.dist-info → agmem-0.2.0.dist-info}/entry_points.txt +0 -0
- {agmem-0.1.6.dist-info → agmem-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {agmem-0.1.6.dist-info → agmem-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agmem
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
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
|
|
@@ -10,7 +10,7 @@ Project-URL: Homepage, https://github.com/vivek-tiwari-vt/agmem
|
|
|
10
10
|
Project-URL: Documentation, https://github.com/vivek-tiwari-vt/agmem#readme
|
|
11
11
|
Project-URL: Repository, https://github.com/vivek-tiwari-vt/agmem
|
|
12
12
|
Project-URL: Bug Tracker, https://github.com/vivek-tiwari-vt/agmem/issues
|
|
13
|
-
Keywords: ai,agent,memory,version-control,git,vcs,llm,merkle,audit,encryption,differential-privacy,trust,multi-agent
|
|
13
|
+
Keywords: ai,agent,memory,version-control,git,vcs,llm,merkle,audit,encryption,differential-privacy,trust,multi-agent,health-monitoring,delta-encoding,ipfs,federated
|
|
14
14
|
Classifier: Development Status :: 3 - Alpha
|
|
15
15
|
Classifier: Intended Audience :: Developers
|
|
16
16
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -71,6 +71,10 @@ Provides-Extra: ipfs
|
|
|
71
71
|
Requires-Dist: requests>=2.28.0; extra == "ipfs"
|
|
72
72
|
Provides-Extra: ipfs-daemon
|
|
73
73
|
Requires-Dist: ipfshttpclient>=0.8.0; extra == "ipfs-daemon"
|
|
74
|
+
Provides-Extra: coordinator
|
|
75
|
+
Requires-Dist: fastapi>=0.100.0; extra == "coordinator"
|
|
76
|
+
Requires-Dist: uvicorn[standard]>=0.22.0; extra == "coordinator"
|
|
77
|
+
Requires-Dist: pydantic>=2.0.0; extra == "coordinator"
|
|
74
78
|
Provides-Extra: all
|
|
75
79
|
Requires-Dist: mcp>=1.0.0; extra == "all"
|
|
76
80
|
Requires-Dist: cryptography>=41.0.0; extra == "all"
|
|
@@ -88,6 +92,7 @@ Requires-Dist: networkx>=3.0; extra == "all"
|
|
|
88
92
|
Requires-Dist: tiktoken>=0.5.0; extra == "all"
|
|
89
93
|
Requires-Dist: presidio-analyzer>=2.2.0; extra == "all"
|
|
90
94
|
Requires-Dist: requests>=2.28.0; extra == "all"
|
|
95
|
+
Requires-Dist: pydantic>=2.0.0; extra == "all"
|
|
91
96
|
Dynamic: author
|
|
92
97
|
Dynamic: home-page
|
|
93
98
|
Dynamic: license-file
|
|
@@ -137,8 +142,9 @@ agmem solves all of these problems with a familiar Git-like interface.
|
|
|
137
142
|
- ✅ **Multi-provider LLM** — OpenAI and Anthropic via `memvcs.core.llm`; config/repo or env; used by gardener, distiller, consistency, merge
|
|
138
143
|
- ✅ **Temporal querying** — Point-in-time and range queries in temporal index; frontmatter timestamps
|
|
139
144
|
- ✅ **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)
|
|
141
|
-
- ✅ **Daemon health** —
|
|
145
|
+
- ✅ **Zero-knowledge proofs** — `agmem prove` (hash/signature-based): keyword containment (Merkle set membership), memory freshness (signed timestamp). **Note:** Current implementation is proof-of-knowledge with known limitations; see docs for migration to true zk-SNARKs.
|
|
146
|
+
- ✅ **Daemon health** — 4-point health monitoring (storage, redundancy, staleness, graph consistency) with periodic checks; visible warnings and JSON reports
|
|
147
|
+
- ✅ **Delta encoding** — 5-10x compression for similar objects using Levenshtein distance and SequenceMatcher; optional feature in pack files
|
|
142
148
|
- ✅ **GPU acceleration** — Vector store detects GPU for embedding model when available
|
|
143
149
|
- ✅ **Optional** — `serve`, `daemon` (watch + auto-commit), `garden` (episode archival), MCP server; install extras as needed
|
|
144
150
|
|
|
@@ -469,8 +475,8 @@ The following 18 capabilities are implemented (or stubbed) per the agmem feature
|
|
|
469
475
|
|
|
470
476
|
| # | Feature | Description |
|
|
471
477
|
|---|---------|-------------|
|
|
472
|
-
| **7** | **Differential privacy** | Epsilon/delta budget per repo in `.mem/privacy_budget.json`. **Usage:** `agmem distill --private`, `agmem garden --private`; blocks when budget exceeded. Config: `differential_privacy.max_epsilon`, `delta`. |
|
|
473
|
-
| **8** | **
|
|
478
|
+
| **7** | **Differential privacy** | Epsilon/delta budget per repo in `.mem/privacy_budget.json`. **Usage:** `agmem distill --private`, `agmem garden --private`; blocks when budget exceeded. Config: `differential_privacy.max_epsilon`, `delta`. **Note:** Now correctly applied to actual facts during extraction, not metadata counts. |
|
|
479
|
+
| **8** | **Cryptographic proofs (proof-of-knowledge)** | Hash/signature-based proofs for keyword containment (Merkle set membership) and memory freshness (signed timestamp). **Command:** `agmem prove --memory <path> --property keyword\|freshness --value <v> [-o out]`. **IMPORTANT:** These are proof-of-knowledge, not true zero-knowledge proofs. Keyword proof leaks word count and allows verifier to test other words. Freshness proof relies on forgeable filesystem mtime. See `memvcs/core/zk_proofs.py` for details and migration path to zk-SNARKs. |
|
|
474
480
|
|
|
475
481
|
### Tier 4 — Storage and distribution
|
|
476
482
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
agmem-0.
|
|
1
|
+
agmem-0.2.0.dist-info/licenses/LICENSE,sha256=X_S6RBErW-F0IDbM3FAEoDB-zxExFnl2m8640rTXphM,1067
|
|
2
2
|
memvcs/__init__.py,sha256=pheWPxubHVcp2N6vk6M7hGXgkJQ06KajbWgCpOlUSJ8,193
|
|
3
3
|
memvcs/cli.py,sha256=YF06oMNjKWUmiNahILmfjrIXgoXzU-5BJFmbunSb8Sc,6075
|
|
4
4
|
memvcs/commands/__init__.py,sha256=A2D6xWaO6epU7iV4QSvqvF5TspnwRyDN7NojmGatPrE,510
|
|
@@ -11,10 +11,10 @@ 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=
|
|
14
|
+
memvcs/commands/daemon.py,sha256=FlPBh41v-S__QhC68FzRYbsumARQgLbP2904k7jaZ6k,12522
|
|
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=
|
|
17
|
+
memvcs/commands/distill.py,sha256=eO9n2KxBaIhdvKBnqcrgYH5aoJ7dR5C2WNge_z6ITlw,3330
|
|
18
18
|
memvcs/commands/federated.py,sha256=Zj4kxHnjdIs1xu4v7B8XosQXNYK8Alv4I0kJQpmJe6Y,1840
|
|
19
19
|
memvcs/commands/fsck.py,sha256=AdJBMLA2myQ0cJJcjUgsYptsE3qvX4JQc9UAwVmSHlA,7772
|
|
20
20
|
memvcs/commands/garden.py,sha256=8JiLe3JRkOhY-N-h-IDuvdJiECiSElnUzXVtxtU2QgY,4050
|
|
@@ -46,6 +46,8 @@ memvcs/commands/timeline.py,sha256=JkuhsQ-6wPWbsjlbJb_qM4mEkxkxcWWzniXXQB4Qtec,4
|
|
|
46
46
|
memvcs/commands/tree.py,sha256=vdULq4vIXA_4gNfMnHn_Y78BwE0sJoeTBOnFJR3WsZ4,4927
|
|
47
47
|
memvcs/commands/verify.py,sha256=04CVW5NYWkUlPJ5z1Kci6dfQFM6UmPTGZh9ZextFLMc,3887
|
|
48
48
|
memvcs/commands/when.py,sha256=bxG_tEYnZNBTl2IPkoxpc2LUEbO_5ev1hRvEzxQQDmc,4773
|
|
49
|
+
memvcs/coordinator/__init__.py,sha256=XJEXEXJFvvhtRInPeyAC9bFNXGbshSrtuK6wZo3wS6g,139
|
|
50
|
+
memvcs/coordinator/server.py,sha256=-kCEyqUi7eLC45qqkH1KCk8zZk8T1NdJyffOY8133ec,7045
|
|
49
51
|
memvcs/core/__init__.py,sha256=dkIC-4tS0GhwV2mZIbofEe8xR8uiFwrxslGf1aXwhYg,493
|
|
50
52
|
memvcs/core/access_index.py,sha256=HhacnzSUASzRV2jhDHkwRFoPS3rtqh9n9yE1VV7JXpk,5596
|
|
51
53
|
memvcs/core/audit.py,sha256=8APkm9Spl_-1rIdyRQz1elyxOeK3nlpwm0CLkpLlhTE,3732
|
|
@@ -55,8 +57,9 @@ memvcs/core/consistency.py,sha256=YOG8xhqZLKZCLbai2rdcP0KxYPNGFv5RRMwrQ6qCeyc,74
|
|
|
55
57
|
memvcs/core/constants.py,sha256=WUjAb50BFcF0mbFi_GNteDLCxLihmViBm9Fb-JMPmbM,220
|
|
56
58
|
memvcs/core/crypto_verify.py,sha256=DTuC7Kfx6z2b8UWOWziBTqP633LrjXbdtGmBBqrJTF0,10424
|
|
57
59
|
memvcs/core/decay.py,sha256=ROGwnqngs7eJNkbKmwyOdij607m73vpmoJqzrIDLBzk,6581
|
|
60
|
+
memvcs/core/delta.py,sha256=obXzojUSc2HaEUqH3L_1LF-GcJ63Wr_yYvIPM8iyeSg,7865
|
|
58
61
|
memvcs/core/diff.py,sha256=koEHTLciIUxYKVJVuvmY0GDXMgDgGZP_qg5RayhF-iE,13226
|
|
59
|
-
memvcs/core/distiller.py,sha256=
|
|
62
|
+
memvcs/core/distiller.py,sha256=QA4acLc005cLac09IvIaog1fJt5IGXWRiSdZq_Ya27g,14086
|
|
60
63
|
memvcs/core/encryption.py,sha256=epny_nlW6ylllv1qxs1mAcFq-PrLIisgfot4llOoAqw,5289
|
|
61
64
|
memvcs/core/federated.py,sha256=vUYMZ0xv80hqGDRKq645Od1i8N33l-pIAkklJbJUlVg,5445
|
|
62
65
|
memvcs/core/gardener.py,sha256=lBWkyE72O-JMiHM-oqrnex9k_xSv7FvztjkOdLdB0Kk,18610
|
|
@@ -65,11 +68,11 @@ memvcs/core/ipfs_remote.py,sha256=xmEO14bn_7Ej-W5jhx2QJyBd-ljj9S2COOxMmcZBiTs,66
|
|
|
65
68
|
memvcs/core/knowledge_graph.py,sha256=GY27e1rgraF2zMpz_jsumdUtpgTRk48yH5CAEQ3TDl4,16416
|
|
66
69
|
memvcs/core/merge.py,sha256=x2eSaxr4f63Eq00FCJ6DDe2TZU8H5yHQpzKzMhYsaFw,19871
|
|
67
70
|
memvcs/core/objects.py,sha256=Xgw1IpQnJLCG5o_7gDHVQ-TNGR9CSpDYWRXzLgLSuec,11006
|
|
68
|
-
memvcs/core/pack.py,sha256=
|
|
71
|
+
memvcs/core/pack.py,sha256=Kq0hyMNroT0MwiS4pVJVuJO9nZ04P3wssep2tADvnpQ,15950
|
|
69
72
|
memvcs/core/pii_scanner.py,sha256=T6gQ1APFrSDk980fjnv4ZMF-UztbJgmUFSwGrwWixEw,10802
|
|
70
73
|
memvcs/core/privacy_budget.py,sha256=fOPlxoKEAmsKtda-OJCrSaKjTyw7ekcqdN7KfRBw1CY,2113
|
|
71
74
|
memvcs/core/refs.py,sha256=4Nx2ZVRa_DzfUZ4O1AwzOHEjoGAEICJKqSd9GxaiD_g,16754
|
|
72
|
-
memvcs/core/remote.py,sha256=
|
|
75
|
+
memvcs/core/remote.py,sha256=sZbAO9JEaDJM96PylB0CjpmR5UxWYdoXlq86sj3R2gU,22228
|
|
73
76
|
memvcs/core/repository.py,sha256=NzC2UFPv6ePxi5lfiSKyZFLclH4bJpWJz88pY7tDiv4,20605
|
|
74
77
|
memvcs/core/schema.py,sha256=_CrEWCdArc0yDJ04GT7fyvjHqkal7gegdFSsFOjVpBc,15287
|
|
75
78
|
memvcs/core/staging.py,sha256=dptdGi_74lhDkcGqGVU39ZyTkb25j-Rnkz0GWi83W1k,7221
|
|
@@ -77,7 +80,7 @@ memvcs/core/temporal_index.py,sha256=81hZHlVElp2UpXjseFVCdDUwxGM45zIU-y1dDlOhFHI
|
|
|
77
80
|
memvcs/core/test_runner.py,sha256=7-0jCvji63JRbVfy3LNQWIQ7VL5weulOoG7SY1-YJbw,11496
|
|
78
81
|
memvcs/core/trust.py,sha256=msx80Cl3bxyQTY8mFUKWY9P6l3zb1s8FafympgHwtpo,3494
|
|
79
82
|
memvcs/core/vector_store.py,sha256=yUAp5BlaAtjkrtsdY1I-vmAp_YIFgJykBoNlp5hcg0I,11063
|
|
80
|
-
memvcs/core/zk_proofs.py,sha256=
|
|
83
|
+
memvcs/core/zk_proofs.py,sha256=tvJnj5oLTNQ_wFIGcMuuVF5faigIX_32U_HojNMoNp0,7623
|
|
81
84
|
memvcs/core/llm/__init__.py,sha256=vnjtE9Xlv9a2pZV88DMT9JaINkZ30hC9VLPL5lJRlps,236
|
|
82
85
|
memvcs/core/llm/anthropic_provider.py,sha256=O1eaCb9r464ajLJz-Gy8lGxBie5ojRUZ_5HdgRXO5KY,1540
|
|
83
86
|
memvcs/core/llm/base.py,sha256=qPzg3KPAMeoyWGc_2JoVR4-plpdft5Rc2g9uO-Z4fJQ,623
|
|
@@ -88,6 +91,8 @@ memvcs/core/storage/base.py,sha256=IK4To8Cb-LHv5ltlaQLdB6LE-69euFK3hNqBtMCe7-g,9
|
|
|
88
91
|
memvcs/core/storage/gcs.py,sha256=-cWuGw1jkFh-Xig-Abmwr9HGwjW5lWQJuF2xcAR1l78,10632
|
|
89
92
|
memvcs/core/storage/local.py,sha256=JAik9nta6RMe4mD7aMtgdFi8M4iZCeTqiP8pPisaO6U,6028
|
|
90
93
|
memvcs/core/storage/s3.py,sha256=tY5rfz8FfkRRNaHOPX7Wk6yXdBBBhKV0Ju2qnBtHxeU,13814
|
|
94
|
+
memvcs/health/__init__.py,sha256=YuxF8hVHJHNilAvVa0maptFLBWm4hcBymMjpA2dFJVU,546
|
|
95
|
+
memvcs/health/monitor.py,sha256=2JQqkR6n0e5L-gsU97FEB3rxjrCNAaGRNFpa7LKZtOg,15545
|
|
91
96
|
memvcs/integrations/__init__.py,sha256=hVtJoFaXt6ErAZwctcSBDZLXRHFs1CNgtltIBQiroQ0,103
|
|
92
97
|
memvcs/integrations/mcp_server.py,sha256=PxBYJnbzPs6bcFH6EmH5jQqbu_9Vy5eSAA8ruWTn2Q4,9061
|
|
93
98
|
memvcs/integrations/web_ui/__init__.py,sha256=MQIfgDKDgPctlcTUjwkwueS_MDsDssVRmIUnpECGS0k,51
|
|
@@ -99,8 +104,8 @@ memvcs/retrieval/recaller.py,sha256=8KY-XjMUz5_vcKf46zI64uk1DEM__u7wM92ShukOtsY,
|
|
|
99
104
|
memvcs/retrieval/strategies.py,sha256=26yxQQubQfjxWQXknfVMxuzPHf2EcZxJg_B99BEdl5c,11458
|
|
100
105
|
memvcs/utils/__init__.py,sha256=8psUzz4Ntv2GzbRebkeVsoyC6Ck-FIwi0_lfYdj5oho,185
|
|
101
106
|
memvcs/utils/helpers.py,sha256=37zg_DcQ2y99b9NSLqxFkglHe13rJXKhFDpEbQ7iLhM,4121
|
|
102
|
-
agmem-0.
|
|
103
|
-
agmem-0.
|
|
104
|
-
agmem-0.
|
|
105
|
-
agmem-0.
|
|
106
|
-
agmem-0.
|
|
107
|
+
agmem-0.2.0.dist-info/METADATA,sha256=Oh9LOeoQR_A9ZXrdrkt-sTFoiYA-peWVHvpVaUYncns,42100
|
|
108
|
+
agmem-0.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
109
|
+
agmem-0.2.0.dist-info/entry_points.txt,sha256=at7eWycgjqOo1wbUMECnXUsNo3gpCkJTU71OzrGLHu0,42
|
|
110
|
+
agmem-0.2.0.dist-info/top_level.txt,sha256=HtMMsKuwLKLOdgF1GxqQztqFM54tTJctVdJuOec6B-4,7
|
|
111
|
+
agmem-0.2.0.dist-info/RECORD,,
|
memvcs/commands/daemon.py
CHANGED
|
@@ -227,11 +227,12 @@ class DaemonCommand:
|
|
|
227
227
|
while running:
|
|
228
228
|
time.sleep(1)
|
|
229
229
|
|
|
230
|
-
# Periodic health check (Merkle/signature
|
|
230
|
+
# Periodic health check (Merkle/signature + operational metrics). Alert only; no destructive action.
|
|
231
231
|
if (
|
|
232
232
|
health_check_interval
|
|
233
233
|
and (time.time() - last_health_check) >= health_check_interval
|
|
234
234
|
):
|
|
235
|
+
# Cryptographic integrity check
|
|
235
236
|
try:
|
|
236
237
|
from ..core.crypto_verify import verify_commit, load_public_key
|
|
237
238
|
|
|
@@ -251,6 +252,41 @@ class DaemonCommand:
|
|
|
251
252
|
sys.stderr.write("Run 'agmem fsck' for safe integrity check.\n")
|
|
252
253
|
except Exception:
|
|
253
254
|
pass
|
|
255
|
+
|
|
256
|
+
# Operational health checks (storage, redundancy, stale memory, graph consistency)
|
|
257
|
+
try:
|
|
258
|
+
from ..health.monitor import HealthMonitor
|
|
259
|
+
|
|
260
|
+
health_monitor = HealthMonitor(repo.root)
|
|
261
|
+
report = health_monitor.perform_all_checks()
|
|
262
|
+
|
|
263
|
+
if report.get("warnings"):
|
|
264
|
+
for warning in report["warnings"]:
|
|
265
|
+
sys.stderr.write(f"Health warning: {warning}\n")
|
|
266
|
+
|
|
267
|
+
# Log summary metrics
|
|
268
|
+
if report.get("storage"):
|
|
269
|
+
storage = report["storage"]
|
|
270
|
+
if "total_size_mb" in storage:
|
|
271
|
+
sys.stderr.write(
|
|
272
|
+
f"Storage: {storage['total_size_mb']:.1f}MB "
|
|
273
|
+
f"({storage['packed_objects']} packed objects)\n"
|
|
274
|
+
)
|
|
275
|
+
if report.get("redundancy"):
|
|
276
|
+
red = report["redundancy"]
|
|
277
|
+
if "redundancy_percentage" in red:
|
|
278
|
+
sys.stderr.write(
|
|
279
|
+
f"Redundancy: {red['redundancy_percentage']:.1f}%\n"
|
|
280
|
+
)
|
|
281
|
+
if report.get("stale_memory"):
|
|
282
|
+
stale = report["stale_memory"]
|
|
283
|
+
if "stale_percentage" in stale:
|
|
284
|
+
sys.stderr.write(
|
|
285
|
+
f"Stale memory: {stale['stale_percentage']:.1f}%\n"
|
|
286
|
+
)
|
|
287
|
+
except Exception:
|
|
288
|
+
pass
|
|
289
|
+
|
|
254
290
|
last_health_check = time.time()
|
|
255
291
|
|
|
256
292
|
if handler.pending:
|
memvcs/commands/distill.py
CHANGED
|
@@ -46,6 +46,11 @@ class DistillCommand:
|
|
|
46
46
|
action="store_true",
|
|
47
47
|
help="Use differential privacy (spend epsilon from budget)",
|
|
48
48
|
)
|
|
49
|
+
parser.add_argument(
|
|
50
|
+
"--no-compress",
|
|
51
|
+
action="store_true",
|
|
52
|
+
help="Disable compression pipeline preprocessing",
|
|
53
|
+
)
|
|
49
54
|
|
|
50
55
|
@staticmethod
|
|
51
56
|
def execute(args) -> int:
|
|
@@ -73,6 +78,7 @@ class DistillCommand:
|
|
|
73
78
|
source_dir=args.source,
|
|
74
79
|
target_dir=args.target,
|
|
75
80
|
create_safety_branch=not args.no_branch,
|
|
81
|
+
use_compression_pipeline=not getattr(args, "no_compress", False),
|
|
76
82
|
use_dp=use_dp,
|
|
77
83
|
dp_epsilon=dp_epsilon,
|
|
78
84
|
dp_delta=dp_delta,
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Minimal Federated Coordinator Server for agmem.
|
|
3
|
+
|
|
4
|
+
Implements the coordinator API from docs/FEDERATED.md:
|
|
5
|
+
- POST /push: Accept agent summaries
|
|
6
|
+
- GET /pull: Return merged summaries
|
|
7
|
+
|
|
8
|
+
This is a reference implementation. For production:
|
|
9
|
+
- Add authentication (API keys, OAuth)
|
|
10
|
+
- Use persistent storage (PostgreSQL, Redis)
|
|
11
|
+
- Add rate limiting
|
|
12
|
+
- Enable HTTPS
|
|
13
|
+
- Scale horizontally
|
|
14
|
+
|
|
15
|
+
Install: pip install "agmem[coordinator]"
|
|
16
|
+
Run: uvicorn memvcs.coordinator.server:app --host 0.0.0.0 --port 8000
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
|
+
from typing import Dict, List, Optional, Any
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
import json
|
|
23
|
+
import hashlib
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from fastapi import FastAPI, HTTPException, Request
|
|
27
|
+
from fastapi.responses import JSONResponse
|
|
28
|
+
from pydantic import BaseModel, Field
|
|
29
|
+
|
|
30
|
+
FASTAPI_AVAILABLE = True
|
|
31
|
+
except ImportError:
|
|
32
|
+
FASTAPI_AVAILABLE = False
|
|
33
|
+
|
|
34
|
+
# Stub for when FastAPI not installed
|
|
35
|
+
class BaseModel:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
def Field(*args, **kwargs):
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Storage: In-memory for simplicity (use Redis/PostgreSQL for production)
|
|
43
|
+
summaries_store: Dict[str, List[Dict[str, Any]]] = {}
|
|
44
|
+
metadata_store: Dict[str, Any] = {
|
|
45
|
+
"coordinator_version": "0.1.6",
|
|
46
|
+
"started_at": datetime.now(timezone.utc).isoformat(),
|
|
47
|
+
"total_pushes": 0,
|
|
48
|
+
"total_agents": 0,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class AgentSummary(BaseModel):
|
|
53
|
+
"""Agent summary for federated push."""
|
|
54
|
+
|
|
55
|
+
agent_id: str = Field(..., description="Unique agent identifier")
|
|
56
|
+
timestamp: str = Field(..., description="ISO 8601 timestamp")
|
|
57
|
+
topic_counts: Dict[str, int] = Field(default_factory=dict, description="Topic -> count")
|
|
58
|
+
fact_hashes: List[str] = Field(default_factory=list, description="SHA-256 hashes of facts")
|
|
59
|
+
metadata: Optional[Dict[str, Any]] = Field(default=None, description="Optional metadata")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class PushRequest(BaseModel):
|
|
63
|
+
"""Request body for /push endpoint."""
|
|
64
|
+
|
|
65
|
+
summary: AgentSummary
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class PullResponse(BaseModel):
|
|
69
|
+
"""Response body for /pull endpoint."""
|
|
70
|
+
|
|
71
|
+
merged_topic_counts: Dict[str, int]
|
|
72
|
+
unique_fact_hashes: List[str]
|
|
73
|
+
contributing_agents: int
|
|
74
|
+
last_updated: str
|
|
75
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if FASTAPI_AVAILABLE:
|
|
79
|
+
app = FastAPI(
|
|
80
|
+
title="agmem Federated Coordinator",
|
|
81
|
+
description="Minimal coordinator for federated agent memory collaboration",
|
|
82
|
+
version="0.1.6",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@app.get("/")
|
|
86
|
+
async def root():
|
|
87
|
+
"""Health check and API info."""
|
|
88
|
+
return {
|
|
89
|
+
"service": "agmem-coordinator",
|
|
90
|
+
"version": metadata_store["coordinator_version"],
|
|
91
|
+
"status": "running",
|
|
92
|
+
"endpoints": {
|
|
93
|
+
"push": "POST /push",
|
|
94
|
+
"pull": "GET /pull",
|
|
95
|
+
"health": "GET /health",
|
|
96
|
+
},
|
|
97
|
+
"started_at": metadata_store["started_at"],
|
|
98
|
+
"total_pushes": metadata_store["total_pushes"],
|
|
99
|
+
"total_agents": metadata_store["total_agents"],
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@app.get("/health")
|
|
103
|
+
async def health():
|
|
104
|
+
"""Health check endpoint."""
|
|
105
|
+
return {
|
|
106
|
+
"status": "healthy",
|
|
107
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@app.post("/push", response_model=Dict[str, Any])
|
|
111
|
+
async def push(request: PushRequest):
|
|
112
|
+
"""
|
|
113
|
+
Accept agent summary and store it.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Confirmation with push timestamp
|
|
117
|
+
"""
|
|
118
|
+
summary = request.summary
|
|
119
|
+
|
|
120
|
+
# Validate timestamp
|
|
121
|
+
try:
|
|
122
|
+
datetime.fromisoformat(summary.timestamp.replace("Z", "+00:00"))
|
|
123
|
+
except ValueError:
|
|
124
|
+
raise HTTPException(
|
|
125
|
+
status_code=400, detail="Invalid timestamp format (expected ISO 8601)"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Store summary by agent_id
|
|
129
|
+
if summary.agent_id not in summaries_store:
|
|
130
|
+
summaries_store[summary.agent_id] = []
|
|
131
|
+
metadata_store["total_agents"] += 1
|
|
132
|
+
|
|
133
|
+
summaries_store[summary.agent_id].append(summary.dict())
|
|
134
|
+
metadata_store["total_pushes"] += 1
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
"status": "accepted",
|
|
138
|
+
"agent_id": summary.agent_id,
|
|
139
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
140
|
+
"message": f"Summary from {summary.agent_id} stored successfully",
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@app.get("/pull", response_model=PullResponse)
|
|
144
|
+
async def pull():
|
|
145
|
+
"""
|
|
146
|
+
Return merged summaries from all agents.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Aggregated topic counts, unique fact hashes, contributing agent count
|
|
150
|
+
"""
|
|
151
|
+
if not summaries_store:
|
|
152
|
+
return PullResponse(
|
|
153
|
+
merged_topic_counts={},
|
|
154
|
+
unique_fact_hashes=[],
|
|
155
|
+
contributing_agents=0,
|
|
156
|
+
last_updated=datetime.now(timezone.utc).isoformat(),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Merge topic counts across all agents
|
|
160
|
+
merged_topics: Dict[str, int] = {}
|
|
161
|
+
all_fact_hashes = set()
|
|
162
|
+
latest_timestamp = None
|
|
163
|
+
|
|
164
|
+
for agent_id, summaries in summaries_store.items():
|
|
165
|
+
for summary in summaries:
|
|
166
|
+
# Aggregate topic counts
|
|
167
|
+
for topic, count in summary.get("topic_counts", {}).items():
|
|
168
|
+
merged_topics[topic] = merged_topics.get(topic, 0) + count
|
|
169
|
+
|
|
170
|
+
# Collect unique fact hashes
|
|
171
|
+
for fact_hash in summary.get("fact_hashes", []):
|
|
172
|
+
all_fact_hashes.add(fact_hash)
|
|
173
|
+
|
|
174
|
+
# Track latest update
|
|
175
|
+
ts = summary.get("timestamp")
|
|
176
|
+
if ts:
|
|
177
|
+
if latest_timestamp is None or ts > latest_timestamp:
|
|
178
|
+
latest_timestamp = ts
|
|
179
|
+
|
|
180
|
+
return PullResponse(
|
|
181
|
+
merged_topic_counts=merged_topics,
|
|
182
|
+
unique_fact_hashes=sorted(list(all_fact_hashes)),
|
|
183
|
+
contributing_agents=len(summaries_store),
|
|
184
|
+
last_updated=latest_timestamp or datetime.now(timezone.utc).isoformat(),
|
|
185
|
+
metadata={
|
|
186
|
+
"total_facts": len(all_fact_hashes),
|
|
187
|
+
"total_topics": len(merged_topics),
|
|
188
|
+
},
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
@app.delete("/admin/reset")
|
|
192
|
+
async def admin_reset(request: Request):
|
|
193
|
+
"""
|
|
194
|
+
Admin endpoint to reset all stored data.
|
|
195
|
+
In production, protect this with authentication!
|
|
196
|
+
"""
|
|
197
|
+
summaries_store.clear()
|
|
198
|
+
metadata_store["total_pushes"] = 0
|
|
199
|
+
metadata_store["total_agents"] = 0
|
|
200
|
+
metadata_store["started_at"] = datetime.now(timezone.utc).isoformat()
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
"status": "reset",
|
|
204
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
else:
|
|
208
|
+
# Stub when FastAPI not available
|
|
209
|
+
app = None
|
|
210
|
+
print("FastAPI not available. Install with: pip install 'agmem[coordinator]'")
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
if __name__ == "__main__":
|
|
214
|
+
if not FASTAPI_AVAILABLE:
|
|
215
|
+
print("Error: FastAPI not installed")
|
|
216
|
+
print("Install with: pip install 'fastapi[all]' uvicorn")
|
|
217
|
+
exit(1)
|
|
218
|
+
|
|
219
|
+
import uvicorn
|
|
220
|
+
|
|
221
|
+
print("Starting agmem Federated Coordinator...")
|
|
222
|
+
print("API docs: http://localhost:8000/docs")
|
|
223
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|