aethergraph 0.1.0a1__py3-none-any.whl → 0.1.0a2__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.
Files changed (267) hide show
  1. aethergraph/__init__.py +4 -10
  2. aethergraph/__main__.py +293 -0
  3. aethergraph/api/v1/__init__.py +0 -0
  4. aethergraph/api/v1/agents.py +46 -0
  5. aethergraph/api/v1/apps.py +70 -0
  6. aethergraph/api/v1/artifacts.py +415 -0
  7. aethergraph/api/v1/channels.py +89 -0
  8. aethergraph/api/v1/deps.py +168 -0
  9. aethergraph/api/v1/graphs.py +259 -0
  10. aethergraph/api/v1/identity.py +25 -0
  11. aethergraph/api/v1/memory.py +353 -0
  12. aethergraph/api/v1/misc.py +47 -0
  13. aethergraph/api/v1/pagination.py +29 -0
  14. aethergraph/api/v1/runs.py +568 -0
  15. aethergraph/api/v1/schemas.py +535 -0
  16. aethergraph/api/v1/session.py +323 -0
  17. aethergraph/api/v1/stats.py +201 -0
  18. aethergraph/api/v1/viz.py +152 -0
  19. aethergraph/config/config.py +22 -0
  20. aethergraph/config/loader.py +3 -2
  21. aethergraph/config/storage.py +209 -0
  22. aethergraph/contracts/__init__.py +0 -0
  23. aethergraph/contracts/services/__init__.py +0 -0
  24. aethergraph/contracts/services/artifacts.py +27 -14
  25. aethergraph/contracts/services/memory.py +45 -17
  26. aethergraph/contracts/services/metering.py +129 -0
  27. aethergraph/contracts/services/runs.py +50 -0
  28. aethergraph/contracts/services/sessions.py +87 -0
  29. aethergraph/contracts/services/state_stores.py +3 -0
  30. aethergraph/contracts/services/viz.py +44 -0
  31. aethergraph/contracts/storage/artifact_index.py +88 -0
  32. aethergraph/contracts/storage/artifact_store.py +99 -0
  33. aethergraph/contracts/storage/async_kv.py +34 -0
  34. aethergraph/contracts/storage/blob_store.py +50 -0
  35. aethergraph/contracts/storage/doc_store.py +35 -0
  36. aethergraph/contracts/storage/event_log.py +31 -0
  37. aethergraph/contracts/storage/vector_index.py +48 -0
  38. aethergraph/core/__init__.py +0 -0
  39. aethergraph/core/execution/forward_scheduler.py +13 -2
  40. aethergraph/core/execution/global_scheduler.py +21 -15
  41. aethergraph/core/execution/step_forward.py +10 -1
  42. aethergraph/core/graph/__init__.py +0 -0
  43. aethergraph/core/graph/graph_builder.py +8 -4
  44. aethergraph/core/graph/graph_fn.py +156 -15
  45. aethergraph/core/graph/graph_spec.py +8 -0
  46. aethergraph/core/graph/graphify.py +146 -27
  47. aethergraph/core/graph/node_spec.py +0 -2
  48. aethergraph/core/graph/node_state.py +3 -0
  49. aethergraph/core/graph/task_graph.py +39 -1
  50. aethergraph/core/runtime/__init__.py +0 -0
  51. aethergraph/core/runtime/ad_hoc_context.py +64 -4
  52. aethergraph/core/runtime/base_service.py +28 -4
  53. aethergraph/core/runtime/execution_context.py +13 -15
  54. aethergraph/core/runtime/graph_runner.py +222 -37
  55. aethergraph/core/runtime/node_context.py +510 -6
  56. aethergraph/core/runtime/node_services.py +12 -5
  57. aethergraph/core/runtime/recovery.py +15 -1
  58. aethergraph/core/runtime/run_manager.py +783 -0
  59. aethergraph/core/runtime/run_manager_local.py +204 -0
  60. aethergraph/core/runtime/run_registration.py +2 -2
  61. aethergraph/core/runtime/run_types.py +89 -0
  62. aethergraph/core/runtime/runtime_env.py +136 -7
  63. aethergraph/core/runtime/runtime_metering.py +71 -0
  64. aethergraph/core/runtime/runtime_registry.py +36 -13
  65. aethergraph/core/runtime/runtime_services.py +194 -6
  66. aethergraph/core/tools/builtins/toolset.py +1 -1
  67. aethergraph/core/tools/toolkit.py +5 -0
  68. aethergraph/plugins/agents/default_chat_agent copy.py +90 -0
  69. aethergraph/plugins/agents/default_chat_agent.py +171 -0
  70. aethergraph/plugins/agents/shared.py +81 -0
  71. aethergraph/plugins/channel/adapters/webui.py +112 -112
  72. aethergraph/plugins/channel/routes/webui_routes.py +367 -102
  73. aethergraph/plugins/channel/utils/slack_utils.py +115 -59
  74. aethergraph/plugins/channel/utils/telegram_utils.py +88 -47
  75. aethergraph/plugins/channel/websockets/weibui_ws.py +172 -0
  76. aethergraph/runtime/__init__.py +15 -0
  77. aethergraph/server/app_factory.py +190 -34
  78. aethergraph/server/clients/channel_client.py +202 -0
  79. aethergraph/server/http/channel_http_routes.py +116 -0
  80. aethergraph/server/http/channel_ws_routers.py +45 -0
  81. aethergraph/server/loading.py +117 -0
  82. aethergraph/server/server.py +131 -0
  83. aethergraph/server/server_state.py +240 -0
  84. aethergraph/server/start.py +227 -66
  85. aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  86. aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  87. aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  88. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  89. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  90. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  91. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  92. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  93. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  94. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  95. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  96. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  97. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  98. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  99. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  100. aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  101. aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  102. aethergraph/server/ui_static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  103. aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  104. aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  105. aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  106. aethergraph/server/ui_static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  107. aethergraph/server/ui_static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  108. aethergraph/server/ui_static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  109. aethergraph/server/ui_static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  110. aethergraph/server/ui_static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  111. aethergraph/server/ui_static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  112. aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  113. aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  114. aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  115. aethergraph/server/ui_static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  116. aethergraph/server/ui_static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  117. aethergraph/server/ui_static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  118. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  119. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  120. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  121. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  122. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  123. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  124. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  125. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  126. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  127. aethergraph/server/ui_static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  128. aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  129. aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  130. aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  131. aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  132. aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  133. aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  134. aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  135. aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  136. aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  137. aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  138. aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  139. aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  140. aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  141. aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  142. aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  143. aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  144. aethergraph/server/ui_static/assets/index-BR5GtXcZ.css +1 -0
  145. aethergraph/server/ui_static/assets/index-CQ0HZZ83.js +400 -0
  146. aethergraph/server/ui_static/index.html +15 -0
  147. aethergraph/server/ui_static/logo.png +0 -0
  148. aethergraph/services/artifacts/__init__.py +0 -0
  149. aethergraph/services/artifacts/facade.py +1239 -132
  150. aethergraph/services/auth/{dev.py → authn.py} +0 -8
  151. aethergraph/services/auth/authz.py +100 -0
  152. aethergraph/services/channel/__init__.py +0 -0
  153. aethergraph/services/channel/channel_bus.py +19 -1
  154. aethergraph/services/channel/factory.py +13 -1
  155. aethergraph/services/channel/ingress.py +311 -0
  156. aethergraph/services/channel/queue_adapter.py +75 -0
  157. aethergraph/services/channel/session.py +502 -19
  158. aethergraph/services/container/default_container.py +122 -43
  159. aethergraph/services/continuations/continuation.py +6 -0
  160. aethergraph/services/continuations/stores/fs_store.py +19 -0
  161. aethergraph/services/eventhub/event_hub.py +76 -0
  162. aethergraph/services/kv/__init__.py +0 -0
  163. aethergraph/services/kv/ephemeral.py +244 -0
  164. aethergraph/services/llm/__init__.py +0 -0
  165. aethergraph/services/llm/generic_client copy.py +691 -0
  166. aethergraph/services/llm/generic_client.py +1288 -187
  167. aethergraph/services/llm/providers.py +3 -1
  168. aethergraph/services/llm/types.py +47 -0
  169. aethergraph/services/llm/utils.py +284 -0
  170. aethergraph/services/logger/std.py +3 -0
  171. aethergraph/services/mcp/__init__.py +9 -0
  172. aethergraph/services/mcp/http_client.py +38 -0
  173. aethergraph/services/mcp/service.py +225 -1
  174. aethergraph/services/mcp/stdio_client.py +41 -6
  175. aethergraph/services/mcp/ws_client.py +44 -2
  176. aethergraph/services/memory/__init__.py +0 -0
  177. aethergraph/services/memory/distillers/llm_long_term.py +234 -0
  178. aethergraph/services/memory/distillers/llm_meta_summary.py +398 -0
  179. aethergraph/services/memory/distillers/long_term.py +225 -0
  180. aethergraph/services/memory/facade/__init__.py +3 -0
  181. aethergraph/services/memory/facade/chat.py +440 -0
  182. aethergraph/services/memory/facade/core.py +447 -0
  183. aethergraph/services/memory/facade/distillation.py +424 -0
  184. aethergraph/services/memory/facade/rag.py +410 -0
  185. aethergraph/services/memory/facade/results.py +315 -0
  186. aethergraph/services/memory/facade/retrieval.py +139 -0
  187. aethergraph/services/memory/facade/types.py +77 -0
  188. aethergraph/services/memory/facade/utils.py +43 -0
  189. aethergraph/services/memory/facade_dep.py +1539 -0
  190. aethergraph/services/memory/factory.py +9 -3
  191. aethergraph/services/memory/utils.py +10 -0
  192. aethergraph/services/metering/eventlog_metering.py +470 -0
  193. aethergraph/services/metering/noop.py +25 -4
  194. aethergraph/services/rag/__init__.py +0 -0
  195. aethergraph/services/rag/facade.py +279 -23
  196. aethergraph/services/rag/index_factory.py +2 -2
  197. aethergraph/services/rag/node_rag.py +317 -0
  198. aethergraph/services/rate_limit/inmem_rate_limit.py +24 -0
  199. aethergraph/services/registry/__init__.py +0 -0
  200. aethergraph/services/registry/agent_app_meta.py +419 -0
  201. aethergraph/services/registry/registry_key.py +1 -1
  202. aethergraph/services/registry/unified_registry.py +74 -6
  203. aethergraph/services/scope/scope.py +159 -0
  204. aethergraph/services/scope/scope_factory.py +164 -0
  205. aethergraph/services/state_stores/serialize.py +5 -0
  206. aethergraph/services/state_stores/utils.py +2 -1
  207. aethergraph/services/viz/__init__.py +0 -0
  208. aethergraph/services/viz/facade.py +413 -0
  209. aethergraph/services/viz/viz_service.py +69 -0
  210. aethergraph/storage/artifacts/artifact_index_jsonl.py +180 -0
  211. aethergraph/storage/artifacts/artifact_index_sqlite.py +426 -0
  212. aethergraph/storage/artifacts/cas_store.py +422 -0
  213. aethergraph/storage/artifacts/fs_cas.py +18 -0
  214. aethergraph/storage/artifacts/s3_cas.py +14 -0
  215. aethergraph/storage/artifacts/utils.py +124 -0
  216. aethergraph/storage/blob/fs_blob.py +86 -0
  217. aethergraph/storage/blob/s3_blob.py +115 -0
  218. aethergraph/storage/continuation_store/fs_cont.py +283 -0
  219. aethergraph/storage/continuation_store/inmem_cont.py +146 -0
  220. aethergraph/storage/continuation_store/kvdoc_cont.py +261 -0
  221. aethergraph/storage/docstore/fs_doc.py +63 -0
  222. aethergraph/storage/docstore/sqlite_doc.py +31 -0
  223. aethergraph/storage/docstore/sqlite_doc_sync.py +90 -0
  224. aethergraph/storage/eventlog/fs_event.py +136 -0
  225. aethergraph/storage/eventlog/sqlite_event.py +47 -0
  226. aethergraph/storage/eventlog/sqlite_event_sync.py +178 -0
  227. aethergraph/storage/factory.py +432 -0
  228. aethergraph/storage/fs_utils.py +28 -0
  229. aethergraph/storage/graph_state_store/state_store.py +64 -0
  230. aethergraph/storage/kv/inmem_kv.py +103 -0
  231. aethergraph/storage/kv/layered_kv.py +52 -0
  232. aethergraph/storage/kv/sqlite_kv.py +39 -0
  233. aethergraph/storage/kv/sqlite_kv_sync.py +98 -0
  234. aethergraph/storage/memory/event_persist.py +68 -0
  235. aethergraph/storage/memory/fs_persist.py +118 -0
  236. aethergraph/{services/memory/hotlog_kv.py → storage/memory/hotlog.py} +8 -2
  237. aethergraph/{services → storage}/memory/indices.py +31 -7
  238. aethergraph/storage/metering/meter_event.py +55 -0
  239. aethergraph/storage/runs/doc_store.py +280 -0
  240. aethergraph/storage/runs/inmen_store.py +82 -0
  241. aethergraph/storage/runs/sqlite_run_store.py +403 -0
  242. aethergraph/storage/sessions/doc_store.py +183 -0
  243. aethergraph/storage/sessions/inmem_store.py +110 -0
  244. aethergraph/storage/sessions/sqlite_session_store.py +399 -0
  245. aethergraph/storage/vector_index/chroma_index.py +138 -0
  246. aethergraph/storage/vector_index/faiss_index.py +179 -0
  247. aethergraph/storage/vector_index/sqlite_index.py +187 -0
  248. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/METADATA +138 -31
  249. aethergraph-0.1.0a2.dist-info/RECORD +356 -0
  250. aethergraph-0.1.0a2.dist-info/entry_points.txt +3 -0
  251. aethergraph/services/artifacts/factory.py +0 -35
  252. aethergraph/services/artifacts/fs_store.py +0 -656
  253. aethergraph/services/artifacts/jsonl_index.py +0 -123
  254. aethergraph/services/artifacts/sqlite_index.py +0 -209
  255. aethergraph/services/memory/distillers/episode.py +0 -116
  256. aethergraph/services/memory/distillers/rolling.py +0 -74
  257. aethergraph/services/memory/facade.py +0 -633
  258. aethergraph/services/memory/persist_fs.py +0 -40
  259. aethergraph/services/rag/index/base.py +0 -27
  260. aethergraph/services/rag/index/faiss_index.py +0 -121
  261. aethergraph/services/rag/index/sqlite_index.py +0 -134
  262. aethergraph-0.1.0a1.dist-info/RECORD +0 -182
  263. aethergraph-0.1.0a1.dist-info/entry_points.txt +0 -2
  264. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/WHEEL +0 -0
  265. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/licenses/LICENSE +0 -0
  266. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/licenses/NOTICE +0 -0
  267. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/top_level.txt +0 -0
@@ -1,123 +0,0 @@
1
- # aethergraph/artifacts/index_jsonl.py
2
- from __future__ import annotations
3
-
4
- import asyncio
5
- import json
6
- import os
7
- import threading
8
- from typing import Literal
9
-
10
- from aethergraph.contracts.services.artifacts import Artifact
11
-
12
-
13
- class JsonlArtifactIndexSync:
14
- """Simple JSONL-based artifact index for small to medium scale use cases.
15
- Not suitable for very large scale (millions of artifacts) due to linear scans.
16
- """
17
-
18
- def __init__(self, path: str, occurrences_path: str | None = None):
19
- self.path = path
20
- self.occ_path = occurrences_path or (os.path.splitext(path)[0] + "_occurrences.jsonl")
21
- os.makedirs(os.path.dirname(path), exist_ok=True)
22
- # small in-memory map for quick lookup / dedup of last write
23
- self._by_id = {}
24
- self._lock = threading.Lock()
25
- if os.path.exists(self.path):
26
- with open(self.path, encoding="utf-8") as f:
27
- for line in f:
28
- if not line.strip():
29
- continue
30
- rec = json.loads(line)
31
- self._by_id[rec["artifact_id"]] = rec
32
-
33
- def upsert(self, a: Artifact) -> None:
34
- """Upsert an artifact record."""
35
- with self._lock:
36
- rec = a.to_dict()
37
- self._by_id[a.artifact_id] = rec
38
- with open(self.path, "a", encoding="utf-8") as f:
39
- f.write(json.dumps(rec) + "\n")
40
-
41
- def list_for_run(self, run_id: str) -> list[Artifact]:
42
- """List all artifacts for a given run_id."""
43
- return [Artifact(**r) for r in self._by_id.values() if r.get("run_id") == run_id]
44
-
45
- def search(
46
- self,
47
- *,
48
- kind: str | None = None,
49
- labels: dict[str, str] | None = None,
50
- metric: str | None = None,
51
- mode: Literal["max", "min"] | None = None,
52
- ) -> list[Artifact]:
53
- """Search artifacts by kind, labels (exact match), and metric (min/max)."""
54
- rows = list(self._by_id.values())
55
- if kind:
56
- rows = [r for r in rows if r.get("kind") == kind]
57
- if labels:
58
- for k, v in labels.items():
59
- rows = [r for r in rows if r.get("labels", {}).get(k) == v]
60
- if metric and mode:
61
- rows = [r for r in rows if metric in r.get("metrics", {})]
62
- rows.sort(key=lambda r: r["metrics"][metric], reverse=(mode == "max"))
63
- return [Artifact(**r) for r in rows]
64
-
65
- def best(
66
- self,
67
- *,
68
- kind: str,
69
- metric: str,
70
- mode: Literal["max", "min"],
71
- filters: dict[str, str] | None = None,
72
- ) -> Artifact | None:
73
- """Get the best artifact by metric with optional filters."""
74
- rows = self.search(kind=kind, labels=filters, metric=metric, mode=mode)
75
- return rows[0] if rows else None
76
-
77
- def pin(self, artifact_id: str, pinned: bool = True) -> None:
78
- """Pin or unpin an artifact by artifact_id."""
79
- if artifact_id in self._by_id:
80
- self._by_id[artifact_id]["pinned"] = bool(pinned)
81
- with open(self.path, "a", encoding="utf-8") as f:
82
- f.write(json.dumps(self._by_id[artifact_id]) + "\n")
83
-
84
- def record_occurrence(self, a: Artifact, extra_labels: dict | None = None):
85
- """
86
- Append-only log that this artifact appeared in this run/node at this time.
87
- Keeps lineage even if bytes are identical across runs.
88
- """
89
- row = {
90
- "artifact_id": a.artifact_id,
91
- "run_id": a.run_id,
92
- "graph_id": a.graph_id,
93
- "node_id": a.node_id,
94
- "tool_name": a.tool_name,
95
- "tool_version": a.tool_version,
96
- "created_at": a.created_at,
97
- "labels": a.labels | (extra_labels or {}),
98
- }
99
- with open(self.occ_path, "a", encoding="utf-8") as f:
100
- f.write(json.dumps(row) + "\n")
101
-
102
-
103
- class JsonlArtifactIndex: # implements AsyncArtifactIndex
104
- def __init__(self, path: str, occurrences_path: str | None = None):
105
- self._sync = JsonlArtifactIndexSync(path, occurrences_path)
106
-
107
- async def upsert(self, a: Artifact) -> None:
108
- await asyncio.to_thread(self._sync.upsert, a)
109
-
110
- async def list_for_run(self, run_id: str) -> list[Artifact]:
111
- return await asyncio.to_thread(self._sync.list_for_run, run_id)
112
-
113
- async def search(self, **kw) -> list[Artifact]:
114
- return await asyncio.to_thread(self._sync.search, **kw)
115
-
116
- async def best(self, **kw) -> Artifact | None:
117
- return await asyncio.to_thread(self._sync.best, **kw)
118
-
119
- async def pin(self, artifact_id: str, pinned: bool = True) -> None:
120
- await asyncio.to_thread(self._sync.pin, artifact_id, pinned)
121
-
122
- async def record_occurrence(self, a: Artifact, extra_labels: dict | None = None):
123
- await asyncio.to_thread(self._sync.record_occurrence, a, extra_labels)
@@ -1,209 +0,0 @@
1
- # aethergraph/artifacts/index_sqlite.py
2
- from __future__ import annotations
3
-
4
- import asyncio
5
- import json
6
- import sqlite3
7
- from typing import Literal
8
-
9
- from aethergraph.contracts.services.artifacts import Artifact
10
- from aethergraph.services.artifacts.jsonl_index import JsonlArtifactIndexSync
11
-
12
-
13
- class SqliteArtifactIndexSync:
14
- """SQLite-based artifact index for medium to large scale use cases.
15
- Suitable for larger scale (millions of artifacts) with indexing.
16
- """
17
-
18
- def __init__(self, db_path: str):
19
- self.db_path = db_path
20
- self._init()
21
-
22
- def _init(self):
23
- con = sqlite3.connect(self.db_path)
24
- cur = con.cursor()
25
- cur.execute("""
26
- CREATE TABLE IF NOT EXISTS artifacts (
27
- artifact_id TEXT PRIMARY KEY,
28
- uri TEXT NOT NULL,
29
- kind TEXT NOT NULL,
30
- bytes INTEGER,
31
- sha256 TEXT,
32
- mime TEXT,
33
- run_id TEXT,
34
- graph_id TEXT,
35
- node_id TEXT,
36
- tool_name TEXT,
37
- tool_version TEXT,
38
- created_at TEXT,
39
- labels TEXT,
40
- metrics TEXT,
41
- params TEXT,
42
- preview_uri TEXT,
43
- pinned INTEGER DEFAULT 0
44
- )""")
45
- cur.execute("CREATE INDEX IF NOT EXISTS idx_kind ON artifacts(kind)")
46
- cur.execute("CREATE INDEX IF NOT EXISTS idx_run ON artifacts(run_id)")
47
- con.commit()
48
- con.close()
49
-
50
- def upsert(self, a: Artifact) -> None:
51
- con = sqlite3.connect(self.db_path)
52
- cur = con.cursor()
53
- cur.execute(
54
- """
55
- INSERT INTO artifacts
56
- (artifact_id, uri, kind, bytes, sha256, mime, run_id, graph_id, node_id,
57
- tool_name, tool_version, created_at, labels, metrics, params, preview_uri, pinned)
58
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
59
- ON CONFLICT(artifact_id) DO UPDATE SET
60
- uri=excluded.uri, kind=excluded.kind, bytes=excluded.bytes,
61
- sha256=excluded.sha256, mime=excluded.mime, run_id=excluded.run_id,
62
- graph_id=excluded.graph_id, node_id=excluded.node_id,
63
- tool_name=excluded.tool_name, tool_version=excluded.tool_version,
64
- created_at=excluded.created_at, labels=excluded.labels,
65
- metrics=excluded.metrics, params=excluded.params,
66
- preview_uri=excluded.preview_uri, pinned=excluded.pinned
67
- """,
68
- (
69
- a.artifact_id,
70
- a.uri,
71
- a.kind,
72
- a.bytes,
73
- a.sha256,
74
- a.mime,
75
- a.run_id,
76
- a.graph_id,
77
- a.node_id,
78
- a.tool_name,
79
- a.tool_version,
80
- a.created_at,
81
- json.dumps(a.labels),
82
- json.dumps(a.metrics),
83
- json.dumps(a.params),
84
- a.preview_uri,
85
- 1 if a.pinned else 0,
86
- ),
87
- )
88
- con.commit()
89
- con.close()
90
-
91
- def list_for_run(self, run_id: str) -> list[Artifact]:
92
- con = sqlite3.connect(self.db_path)
93
- cur = con.cursor()
94
- cur.execute("SELECT * FROM artifacts WHERE run_id=? ORDER BY created_at", (run_id,))
95
- rows = cur.fetchall()
96
- con.close()
97
- return [self._row_to_artifact(r) for r in rows]
98
-
99
- def search(
100
- self,
101
- *,
102
- kind: str | None = None,
103
- labels: dict[str, str] | None = None,
104
- metric: str | None = None,
105
- mode: Literal["max", "min"] | None = None,
106
- ) -> list[Artifact]:
107
- con = sqlite3.connect(self.db_path)
108
- cur = con.cursor()
109
- q = "SELECT * FROM artifacts WHERE 1=1"
110
- args = []
111
- if kind:
112
- q += " AND kind=?"
113
- args.append(kind)
114
- # naive label filter: all requested label kv must be contained in labels json
115
- if labels:
116
- for k, v in labels.items():
117
- q += " AND json_extract(labels, ?) = ?"
118
- args += (f"$.{k}", v)
119
- cur.execute(q, args)
120
- rows = [self._row_to_artifact(r) for r in cur.fetchall()]
121
- con.close()
122
- if metric and mode and rows:
123
- rows = [r for r in rows if r.metrics and metric in r.metrics]
124
- reverse = mode == "max"
125
- rows.sort(key=lambda a: a.metrics.get(metric, float("-inf")), reverse=reverse)
126
- return rows
127
-
128
- def best(
129
- self,
130
- *,
131
- kind: str,
132
- metric: str,
133
- mode: Literal["max", "min"],
134
- filters: dict[str, str] | None = None,
135
- ) -> Artifact | None:
136
- rows = self.search(kind=kind, labels=filters, metric=metric, mode=mode)
137
- return rows[0] if rows else None
138
-
139
- def pin(self, artifact_id: str, pinned: bool = True) -> None:
140
- con = sqlite3.connect(self.db_path)
141
- cur = con.cursor()
142
- cur.execute(
143
- "UPDATE artifacts SET pinned=? WHERE artifact_id=?", (1 if pinned else 0, artifact_id)
144
- )
145
- con.commit()
146
- con.close()
147
-
148
- def _row_to_artifact(self, r) -> Artifact:
149
- (
150
- artifact_id,
151
- uri,
152
- kind,
153
- bytes_,
154
- sha256,
155
- mime,
156
- run_id,
157
- graph_id,
158
- node_id,
159
- tool_name,
160
- tool_version,
161
- created_at,
162
- labels,
163
- metrics,
164
- params,
165
- preview_uri,
166
- pinned,
167
- ) = r
168
- return Artifact(
169
- artifact_id=artifact_id,
170
- uri=uri,
171
- kind=kind,
172
- bytes=bytes_,
173
- sha256=sha256,
174
- mime=mime,
175
- run_id=run_id,
176
- graph_id=graph_id,
177
- node_id=node_id,
178
- tool_name=tool_name,
179
- tool_version=tool_version,
180
- created_at=created_at,
181
- labels=json.loads(labels or "{}"),
182
- metrics=json.loads(metrics or "{}"),
183
- params=json.loads(params or "{}"),
184
- preview_uri=preview_uri,
185
- pinned=bool(pinned),
186
- )
187
-
188
-
189
- class SqliteArtifactIndex: # implements AsyncArtifactIndex
190
- def __init__(self, path: str, occurrences_path: str | None = None):
191
- self._sync = JsonlArtifactIndexSync(path, occurrences_path)
192
-
193
- async def upsert(self, a: Artifact) -> None:
194
- await asyncio.to_thread(self._sync.upsert, a)
195
-
196
- async def list_for_run(self, run_id: str) -> list[Artifact]:
197
- return await asyncio.to_thread(self._sync.list_for_run, run_id)
198
-
199
- async def search(self, **kw) -> list[Artifact]:
200
- return await asyncio.to_thread(self._sync.search, **kw)
201
-
202
- async def best(self, **kw) -> Artifact | None:
203
- return await asyncio.to_thread(self._sync.best, **kw)
204
-
205
- async def pin(self, artifact_id: str, pinned: bool = True) -> None:
206
- await asyncio.to_thread(self._sync.pin, artifact_id, pinned)
207
-
208
- async def record_occurrence(self, a: Artifact, extra_labels: dict | None = None):
209
- await asyncio.to_thread(self._sync.record_occurrence, a, extra_labels)
@@ -1,116 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import hashlib
4
- import json
5
- import time
6
- from typing import Any
7
-
8
- from aethergraph.contracts.services.memory import Distiller, Event, HotLog, Indices, Persistence
9
-
10
-
11
- def _now_iso() -> str:
12
- return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
13
-
14
-
15
- def _stable_event_id(parts: dict[str, Any]) -> str:
16
- blob = json.dumps(parts, sort_keys=True, ensure_ascii=False).encode("utf-8")
17
- return hashlib.sha256(blob).hexdigest()[:24]
18
-
19
-
20
- def _episode_uri(sessiorun_idn_id: str, tool: str, run_id: str) -> str:
21
- safe = tool.replace("/", "_")
22
- return f"file://mem/{run_id}/episodes/{safe}/{run_id}.json"
23
-
24
-
25
- class EpisodeSummarizer(Distiller):
26
- """
27
- Aggregate all events for (tool, run_id) into a compact episode summary:
28
- - sources: event_ids
29
- - merged metrics (last-write-wins)
30
- - notes: last N textual notes
31
- Writes a JSON summary artifact and emits a lightweight run_summary event.
32
- """
33
-
34
- def __init__(
35
- self, *, include_metrics: bool = True, note_limit: int = 8, note_chars: int = 2000
36
- ):
37
- self.include_metrics = include_metrics
38
- self.note_limit = note_limit
39
- self.note_chars = note_chars
40
-
41
- async def distill(
42
- self,
43
- run_id: str,
44
- *,
45
- hotlog: HotLog,
46
- persistence: Persistence,
47
- indices: Indices,
48
- tool: str,
49
- **kw,
50
- ) -> dict[str, Any]:
51
- # Pull a reasonable window from hot memory; filter in-process.
52
- # (If needed later, add a Persistence scan by day.)
53
- events = await hotlog.recent(
54
- run_id, kinds=["tool_start", "tool_result", "error", "run_summary"], limit=400
55
- )
56
-
57
- eps = [e for e in events if e.run_id == run_id and (e.tool or "") == tool]
58
- if not eps:
59
- return {}
60
-
61
- srcs: list[str] = []
62
- notes: list[str] = []
63
- metrics: dict[str, float] = {}
64
-
65
- for e in eps:
66
- if e.event_id:
67
- srcs.append(e.event_id)
68
- if self.include_metrics and e.metrics:
69
- metrics.update(e.metrics) # simple merge; last-write-wins
70
- if e.text:
71
- notes.append(e.text)
72
-
73
- ts = _now_iso()
74
- summary = {
75
- "kind": "episode_summary",
76
- "run_id": run_id,
77
- "tool": tool,
78
- "ts": ts,
79
- "sources": srcs,
80
- "metrics": metrics,
81
- "notes": notes[-self.note_limit :],
82
- }
83
-
84
- uri = _episode_uri(run_id, tool, run_id)
85
- await persistence.save_json(uri, summary)
86
-
87
- # Emit a compact run_summary event
88
- compact_text = "\n".join(summary["notes"][-self.note_limit :])[: self.note_chars]
89
- evt_base = {
90
- "run_id": run_id,
91
- "tool": tool,
92
- "kind": "run_summary",
93
- "severity": 1,
94
- "tags": ["summary", "episode"],
95
- }
96
- eid = _stable_event_id(
97
- {
98
- "ts": ts,
99
- "run_id": run_id,
100
- "tool": tool,
101
- "kind": "run_summary",
102
- "text": compact_text[:200],
103
- }
104
- )
105
- evt = Event(
106
- event_id=eid,
107
- ts=ts,
108
- text=compact_text,
109
- metrics={"notes": len(notes)},
110
- signal=0.5,
111
- **evt_base,
112
- )
113
- await hotlog.append(run_id, evt, ttl_s=7 * 24 * 3600, limit=1000)
114
- await persistence.append_event(run_id, evt)
115
-
116
- return {"uri": uri, "sources": srcs, "metrics": metrics}
@@ -1,74 +0,0 @@
1
- import time
2
- from typing import Any
3
-
4
- from aethergraph.contracts.services.memory import Distiller, Event, HotLog, Indices, Persistence
5
-
6
-
7
- def _now_iso():
8
- return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
9
-
10
-
11
- def ar_summary_uri(run_id: str, tag: str, ts: str) -> str:
12
- # Save summaries under the same base "mem/<run_id>/..." tree as append_event,
13
- # but using a file:// URI so FSPersistence can handle it.
14
- safe_ts = ts.replace(":", "-")
15
- return f"file://mem/{run_id}/summaries/{tag}/{safe_ts}.json"
16
-
17
-
18
- class RollingSummarizer(Distiller):
19
- def __init__(
20
- self, *, max_turns: int = 20, min_signal: float = 0.25, turn_kinds: list[str] | None = None
21
- ):
22
- self.max_turns = max_turns
23
- self.min_signal = min_signal
24
- self.turn_kinds = turn_kinds or ["user_msg", "assistant_msg"]
25
-
26
- async def distill(
27
- self, run_id: str, *, hotlog: HotLog, persistence: Persistence, indices: Indices, **kw
28
- ) -> dict[str, Any]:
29
- turns = await hotlog.recent(run_id, kinds=self.turn_kinds, limit=self.max_turns * 2)
30
- kept = [t for t in turns if (t.signal or 0.0) >= self.min_signal]
31
- if not kept:
32
- return {}
33
-
34
- lines = []
35
- srcs: list[str] = []
36
- for t in kept[-self.max_turns :]:
37
- role = "User" if t.kind == "user_msg" else "Assistant"
38
- if t.text:
39
- lines.append(f"{role}: {t.text}")
40
- srcs.append(t.event_id)
41
- digest_text = "\n".join(lines)
42
- ts = _now_iso()
43
- summary = {
44
- "kind": "rolling_summary",
45
- "run_id": run_id,
46
- "ts": ts,
47
- "sources": srcs,
48
- "text": digest_text,
49
- }
50
-
51
- uri = ar_summary_uri(run_id, "rolling", ts)
52
-
53
- await persistence.save_json(uri=uri, obj=summary)
54
-
55
- evt = Event(
56
- event_id="",
57
- ts=ts,
58
- run_id=run_id,
59
- kind="rolling_summary",
60
- severity=1,
61
- signal=0.5,
62
- text=digest_text,
63
- metrics={"num_turns": len(kept)},
64
- tags=["summary"],
65
- )
66
-
67
- from aethergraph.services.memory.facade import stable_event_id
68
-
69
- evt.event_id = stable_event_id(
70
- {"ts": ts, "run_id": run_id, "kind": "rolling_summary", "text": digest_text[:200]}
71
- )
72
- await hotlog.append(run_id, evt, ttl_s=7 * 24 * 3600, limit=1000)
73
- await persistence.append_event(run_id, evt)
74
- return {"uri": uri, "sources": srcs}