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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agmem
3
- Version: 0.1.6
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** — Periodic Merkle verification in daemon loop; safe auto-remediation hooks
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** | **Zero-knowledge proofs** | zk-SNARK-style proofs for keyword containment and memory freshness. **Command:** `agmem prove --memory <path> --property keyword|freshness --value <v> [-o out]` (stub). |
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.6.dist-info/licenses/LICENSE,sha256=X_S6RBErW-F0IDbM3FAEoDB-zxExFnl2m8640rTXphM,1067
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=fV6aIz8bFP9VwB_MLudAb_lhhhBxSe2aV-Wjqe-nvPw,10708
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=reOldqg0lMgqIlpYEIKYfN_TxNwsjU9RnI8Uah1VqTQ,3088
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=ZOmrwYYhOla8rZncQP_0y0Ab9jCl3GjtdoH82HkXlsw,12621
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=nTzpPNNk47e7_oN3z7bjaichpzI7q-ql2E8eI2UuGyM,9828
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=1PINc6qYBIHRkNLMS8MLWM5DJIrX81uIfRrV6fXwwro,19495
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=j9AyHucYe9tOSrlxDeUMGgpRHMvNFOl8s4Q0AQHLKP0,5514
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.1.6.dist-info/METADATA,sha256=5Pwa47IVpid1YYz60_uGxR1xz_uzPJ6HmYVPnv1v3P4,41042
103
- agmem-0.1.6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
104
- agmem-0.1.6.dist-info/entry_points.txt,sha256=at7eWycgjqOo1wbUMECnXUsNo3gpCkJTU71OzrGLHu0,42
105
- agmem-0.1.6.dist-info/top_level.txt,sha256=HtMMsKuwLKLOdgF1GxqQztqFM54tTJctVdJuOec6B-4,7
106
- agmem-0.1.6.dist-info/RECORD,,
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, optional). Alert only; no destructive action.
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:
@@ -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,5 @@
1
+ """Coordinator package for federated collaboration."""
2
+
3
+ from .server import app, FASTAPI_AVAILABLE
4
+
5
+ __all__ = ["app", "FASTAPI_AVAILABLE"]
@@ -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)