aethergraph 0.1.0a1__py3-none-any.whl → 0.1.0a3__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 +296 -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 +196 -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.0a3.dist-info}/METADATA +138 -31
  249. aethergraph-0.1.0a3.dist-info/RECORD +356 -0
  250. aethergraph-0.1.0a3.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.0a3.dist-info}/WHEEL +0 -0
  265. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/licenses/LICENSE +0 -0
  266. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/licenses/NOTICE +0 -0
  267. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,204 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timezone
4
+ from typing import Any
5
+ from uuid import uuid4
6
+
7
+ from aethergraph.contracts.errors.errors import GraphHasPendingWaits
8
+ from aethergraph.contracts.services.runs import RunStore
9
+ from aethergraph.core.runtime.run_types import RunRecord, RunStatus
10
+ from aethergraph.core.runtime.runtime_metering import current_metering
11
+ from aethergraph.core.runtime.runtime_registry import current_registry
12
+ from aethergraph.services.registry.unified_registry import UnifiedRegistry
13
+
14
+
15
+ def _utcnow() -> datetime:
16
+ return datetime.now(tz=timezone.utc)
17
+
18
+
19
+ def _is_task_graph(obj: Any) -> bool:
20
+ return hasattr(obj, "spec") and hasattr(obj, "io_signature")
21
+
22
+
23
+ def _is_graphfn(obj: Any) -> bool:
24
+ from aethergraph.core.graph.graph_fn import GraphFunction # adjust path
25
+
26
+ return isinstance(obj, GraphFunction)
27
+
28
+
29
+ class RunManager:
30
+ """
31
+ Core coordinator for runs:
32
+
33
+ - Resolves targets from the UnifiedRegistry.
34
+ - Calls run_or_resume_async for TaskGraph/GraphFunction.
35
+ - Records metadata in RunStore.
36
+ - TODO: (Later) can coordinate cancellation via sched_registry or best effort with graph_fn.
37
+ """
38
+
39
+ def __init__(
40
+ self, *, run_store: RunStore | None = None, registry: UnifiedRegistry | None = None
41
+ ):
42
+ self._store = run_store
43
+ self._registry = registry
44
+
45
+ def registry(self) -> UnifiedRegistry:
46
+ return self._registry or current_registry()
47
+
48
+ async def _resolve_target(self, graph_id: str) -> Any:
49
+ reg = self.registry()
50
+ # Try static TaskGraph
51
+ try:
52
+ return reg.get_graph(name=graph_id, version=None)
53
+ except KeyError:
54
+ pass
55
+ # Try GraphFunction
56
+ try:
57
+ return reg.get_graphfn(name=graph_id, version=None)
58
+ except KeyError:
59
+ pass
60
+ raise KeyError(f"Graph '{graph_id}' not found")
61
+
62
+ async def start_run(
63
+ self,
64
+ graph_id: str,
65
+ *,
66
+ inputs: dict[str, Any],
67
+ run_id: str | None = None,
68
+ tags: list[str] | None = None,
69
+ user_id: str | None = None,
70
+ org_id: str | None = None,
71
+ ) -> tuple[RunRecord, dict[str, Any] | None, bool, list[dict[str, Any]]]:
72
+ """
73
+ The main entrypoint for the API layer.
74
+
75
+ Returns:
76
+ (record, outputs, has_waits, continuations)
77
+ """
78
+ from aethergraph.core.runtime.graph_runner import run_or_resume_async
79
+
80
+ tags = tags or []
81
+ target = await self._resolve_target(graph_id)
82
+ rid = run_id or f"run-{uuid4().hex[:8]}"
83
+
84
+ started_at = _utcnow()
85
+
86
+ if _is_task_graph(target):
87
+ kind = "taskgraph"
88
+ elif _is_graphfn(target):
89
+ kind = "graphfn"
90
+ else:
91
+ kind = "other"
92
+
93
+ # Initial record
94
+ record = RunRecord(
95
+ run_id=rid,
96
+ graph_id=graph_id,
97
+ kind=kind,
98
+ status=RunStatus.running, # or pending, but we jump straight to running
99
+ started_at=started_at,
100
+ tags=list(tags),
101
+ user_id=user_id,
102
+ org_id=org_id,
103
+ )
104
+
105
+ if self._store is not None:
106
+ await self._store.create(record)
107
+
108
+ outputs: dict[str, Any] | None = None
109
+ has_waits = False
110
+ continuations: list[dict[str, Any]] = []
111
+ error_msg: str | None = None
112
+
113
+ try:
114
+ result = await run_or_resume_async(target, inputs or {}, run_id=rid)
115
+ # If we get here without GraphHasPendingWaits, run is completed
116
+ outputs = result if isinstance(result, dict) else {"result": result}
117
+ record.status = RunStatus.succeeded
118
+ record.finished_at = _utcnow()
119
+
120
+ except GraphHasPendingWaits as e:
121
+ # Graph quiesced with pending waits
122
+ record.status = RunStatus.running
123
+ has_waits = True
124
+ continuations = getattr(e, "continuations", [])
125
+ # outputs stay None
126
+
127
+ except Exception as exc:
128
+ record.status = RunStatus.failed
129
+ record.finished_at = _utcnow()
130
+ error_msg = str(exc)
131
+ record.error = error_msg
132
+ # TODO: log here with current_logger_factory if desired
133
+ import logging
134
+
135
+ logging.getLogger("aethergraph.runtime.run_manager").exception(
136
+ "Run %s failed with exception with %s", rid, error_msg
137
+ )
138
+
139
+ if self._store is not None:
140
+ await self._store.update_status(
141
+ rid,
142
+ record.status,
143
+ finished_at=record.finished_at,
144
+ error=error_msg,
145
+ )
146
+
147
+ meter = current_metering()
148
+ # Duration: if finished_at is None, use now()
149
+ finished_at = record.finished_at or _utcnow()
150
+ duration_s = (finished_at - started_at).total_seconds()
151
+ # Map Runstatus + waits to status string for metering
152
+ if has_waits:
153
+ meter_status = "waiting"
154
+ else:
155
+ status_str = getattr(record.status, "value", str(record.status))
156
+ meter_status = status_str
157
+
158
+ try:
159
+ await meter.record_run(
160
+ user_id=user_id,
161
+ org_id=org_id,
162
+ run_id=rid,
163
+ graph_id=graph_id,
164
+ status=meter_status,
165
+ duration_s=duration_s,
166
+ )
167
+ except Exception:
168
+ # Never fail the run due to metering issues
169
+ import logging
170
+
171
+ logging.getLogger("aethergraph.runtime.run_manager").exception(
172
+ "Error recording run metering for run_id=%s", rid
173
+ )
174
+
175
+ return record, outputs, has_waits, continuations
176
+
177
+ async def get_record(self, run_id: str) -> RunRecord | None:
178
+ if self._store is None:
179
+ return None
180
+ return await self._store.get(run_id)
181
+
182
+ async def list_records(
183
+ self,
184
+ *,
185
+ graph_id: str | None = None,
186
+ status: RunStatus | None = None,
187
+ limit: int = 100,
188
+ ) -> list[RunRecord]:
189
+ if self._store is None:
190
+ return []
191
+ return await self._store.list(graph_id=graph_id, status=status, limit=limit)
192
+
193
+ # Placeholder for future cancellation
194
+ async def cancel_run(self, run_id: str) -> RunRecord | None:
195
+ """
196
+ Later: use container.sched_registry to find scheduler and request cancellation.
197
+
198
+ For now, it's a stub that just reads the current record.
199
+ """
200
+ # Future:
201
+ # - container = current_services()
202
+ # - sched = container.sched_registry.get(run_id)
203
+ # - if sched: sched.request_cancel() or sched.terminate()
204
+ return await self.get_record(run_id)
@@ -17,8 +17,8 @@ class RunRegistrationGuard(AbstractContextManager):
17
17
  reg = self.container.sched_registry
18
18
  existing = reg.get(self.run_id)
19
19
  if existing is not None and existing is not self.scheduler:
20
- # Be explicit to avoid silent clobbering
21
- raise RuntimeError(f"Scheduler already registered for run_id={self.run_id}")
20
+ # Do nothing if already registered
21
+ return self
22
22
  reg.register(self.run_id, self.scheduler)
23
23
  self._did_reg = True
24
24
  return self
@@ -0,0 +1,89 @@
1
+ from dataclasses import dataclass, field
2
+ from datetime import datetime
3
+ from enum import Enum
4
+ from typing import Any
5
+
6
+ # used to represent the status of a run, primiarily used in endpoint with RunManager
7
+
8
+
9
+ class RunStatus(str, Enum):
10
+ pending = "pending"
11
+ running = "running"
12
+ succeeded = "succeeded"
13
+ failed = "failed"
14
+ waiting = "waiting"
15
+ canceled = "canceled"
16
+ cancellation_requested = "cancellation_requested"
17
+
18
+
19
+ class RunOrigin(str, Enum):
20
+ app = "app" # launched from an application UI
21
+ agent = "agent" # launched by an AI agent
22
+ chat = "chat" # launched from a chat interface
23
+ playground = "playground" # launched from a playground environment (sidecar/SDK/local dev)
24
+ api = "api" # launched from an API call
25
+ system = "system" # launched from a system process (internal maintenance, cron job, etc.)
26
+
27
+
28
+ class RunVisibility(str, Enum):
29
+ normal = "normal" # visible to all users with access to the org/client
30
+ inline = "inline" # hidden from run listings, only shown in session / debug views
31
+ hidden = "hidden" # hidden from all UIs (used in quick tests, ephemeral runs, etc.)
32
+
33
+
34
+ class RunImportance(str, Enum):
35
+ normal = "normal" # standard run
36
+ ephemeral = "ephemeral" # low-importance run, noisy or temporary (may be pruned sooner)
37
+
38
+
39
+ @dataclass
40
+ class RunRecord:
41
+ """
42
+ Core-level representation of a run.
43
+
44
+ This is independent from any Pydantic model used by the HTTP API.
45
+ """
46
+
47
+ run_id: str
48
+ graph_id: str
49
+ kind: str # "taskgraph" | "graphfn" | other in the future
50
+ status: RunStatus
51
+ started_at: datetime
52
+ finished_at: datetime | None = None
53
+
54
+ tags: list[str] = field(default_factory=list)
55
+ user_id: str | None = None
56
+ org_id: str | None = None
57
+ error: str | None = None
58
+ meta: dict[str, Any] = field(default_factory=dict)
59
+
60
+ session_id: str | None = None
61
+ origin: RunOrigin = RunOrigin.app
62
+ visibility: RunVisibility = RunVisibility.normal
63
+ importance: RunImportance = RunImportance.normal
64
+
65
+ # optional agent/app linkage
66
+ agent_id: str | None = None
67
+ app_id: str | None = None
68
+
69
+ # Artifact statistics
70
+ artifact_count: int = 0
71
+ first_artifact_at: datetime | None = None
72
+ last_artifact_at: datetime | None = None
73
+
74
+ # Optional: keep a small rolling window of recent artifact IDs
75
+ recent_artifact_ids: list[str] = field(default_factory=list)
76
+
77
+ def __item__(self, key: str) -> Any:
78
+ return getattr(self, key)
79
+
80
+ def get(self, key: str, default: Any = None) -> Any:
81
+ return getattr(self, key, default)
82
+
83
+
84
+ # Session-related run types
85
+ class SessionKind(str, Enum):
86
+ chat = "chat"
87
+ playground = "playground"
88
+ notebook = "notebook"
89
+ pipline = "pipeline" # future
@@ -2,9 +2,11 @@ from collections.abc import Callable
2
2
  from dataclasses import dataclass, field
3
3
  from typing import Any
4
4
 
5
+ from aethergraph.api.v1.deps import RequestIdentity
6
+ from aethergraph.contracts.storage.artifact_index import AsyncArtifactIndex
7
+
5
8
  # ---- artifact services ----
6
- from aethergraph.services.artifacts.fs_store import FSArtifactStore # AsyncArtifactStore
7
- from aethergraph.services.artifacts.jsonl_index import JsonlArtifactIndex # AsyncArtifactIndex
9
+ from aethergraph.contracts.storage.artifact_store import AsyncArtifactStore
8
10
 
9
11
  # ---- channel services ----
10
12
  from aethergraph.services.channel.channel_bus import ChannelBus
@@ -16,7 +18,9 @@ from aethergraph.services.continuations.stores.fs_store import (
16
18
 
17
19
  # ---- memory services ----
18
20
  from aethergraph.services.memory.facade import MemoryFacade
21
+ from aethergraph.services.rag.node_rag import NodeRAG
19
22
  from aethergraph.services.resume.router import ResumeRouter
23
+ from aethergraph.services.viz.facade import VizFacade
20
24
  from aethergraph.services.waits.wait_registry import WaitRegistry
21
25
 
22
26
  from ..graph.task_node import TaskNodeRuntime
@@ -31,9 +35,15 @@ class RuntimeEnv:
31
35
 
32
36
  run_id: str
33
37
  graph_id: str | None = None
38
+ session_id: str | None = None
39
+ identity: RequestIdentity | None = None
34
40
  graph_inputs: dict[str, Any] = field(default_factory=dict)
35
41
  outputs_by_node: dict[str, dict[str, Any]] = field(default_factory=dict)
36
42
 
43
+ # agent and app ids
44
+ agent_id: str | None = None # for agent-invoked runs
45
+ app_id: str | None = None # for app-invoked runs
46
+
37
47
  # container (DI)
38
48
  container: DefaultContainer = field(default_factory=get_container)
39
49
 
@@ -70,11 +80,11 @@ class RuntimeEnv:
70
80
  return self.container.wait_registry
71
81
 
72
82
  @property
73
- def artifacts(self) -> FSArtifactStore:
83
+ def artifacts(self) -> AsyncArtifactStore:
74
84
  return self.container.artifacts
75
85
 
76
86
  @property
77
- def artifact_index(self) -> JsonlArtifactIndex:
87
+ def artifact_index(self) -> AsyncArtifactIndex:
78
88
  return self.container.artifact_index
79
89
 
80
90
  @property
@@ -104,15 +114,43 @@ class RuntimeEnv:
104
114
  "run_id": self.run_id,
105
115
  "graph_id": self.graph_id,
106
116
  "node_id": node.node_id,
107
- "agent_id": getattr(node, "tool_name", None),
108
117
  "tags": [],
109
118
  "entities": [],
110
119
  }
120
+
121
+ level, custom_scope_id = self._resolve_memory_config()
122
+ mem_scope = (
123
+ self.container.scope_factory.for_memory(
124
+ identity=self.identity,
125
+ run_id=self.run_id,
126
+ graph_id=self.graph_id,
127
+ node_id=node.node_id,
128
+ session_id=self.session_id,
129
+ level=level,
130
+ custom_scope_id=custom_scope_id,
131
+ )
132
+ if self.container.scope_factory
133
+ else None
134
+ )
135
+
111
136
  mem: MemoryFacade = self.memory_factory.for_session(
112
137
  run_id=self.run_id,
113
138
  graph_id=self.graph_id,
114
139
  node_id=node.node_id,
115
- agent_id=defaults["agent_id"],
140
+ session_id=self.session_id,
141
+ scope=mem_scope,
142
+ )
143
+
144
+ node_scope = (
145
+ self.container.scope_factory.for_node(
146
+ identity=self.identity,
147
+ run_id=self.run_id,
148
+ graph_id=self.graph_id,
149
+ node_id=node.node_id,
150
+ session_id=self.session_id,
151
+ )
152
+ if self.container.scope_factory
153
+ else None
116
154
  )
117
155
 
118
156
  from aethergraph.services.artifacts.facade import ArtifactFacade
@@ -125,8 +163,30 @@ class RuntimeEnv:
125
163
  tool_version=node.tool_version, # to be filled from node if available
126
164
  store=self.artifacts,
127
165
  index=self.artifact_index,
166
+ scope=node_scope,
167
+ )
168
+
169
+ # ------- Viz Service tied to this node/run -------'
170
+ vis_facade = VizFacade(
171
+ run_id=self.run_id,
172
+ graph_id=self.graph_id,
173
+ node_id=node.node_id,
174
+ tool_name=node.tool_name,
175
+ tool_version=node.tool_version,
176
+ artifacts=artifact_facade,
177
+ viz_service=self.container.viz_service,
178
+ scope=node_scope,
128
179
  )
129
180
 
181
+ # ------- RAG Facade in Memory tied to this node/run -------'
182
+ rag_for_node = None
183
+ if self.rag_facade is not None and node_scope is not None:
184
+ rag_for_node = NodeRAG(
185
+ rag=self.rag_facade,
186
+ scope=node_scope,
187
+ default_scope_id=(mem_scope.memory_scope_id() if mem_scope else None),
188
+ )
189
+
130
190
  services = NodeServices(
131
191
  channels=self.channels,
132
192
  continuation_store=self.continuation_store,
@@ -137,13 +197,19 @@ class RuntimeEnv:
137
197
  kv=self.container.kv_hot, # keep using hot kv for ephemeral
138
198
  memory=self.memory_factory, # factory (for other sessions if needed)
139
199
  memory_facade=mem, # bound memory for this run/node
200
+ viz=vis_facade,
140
201
  llm=self.llm_service, # LLMService
141
- rag=self.rag_facade, # RAGService
202
+ rag=rag_for_node, # RAGService
142
203
  mcp=self.mcp_service, # MCPService
204
+ run_manager=self.container.run_manager, # RunManager
143
205
  )
144
206
  return ExecutionContext(
145
207
  run_id=self.run_id,
208
+ session_id=self.session_id,
209
+ identity=self.identity,
146
210
  graph_id=self.graph_id,
211
+ agent_id=self.agent_id,
212
+ app_id=self.app_id,
147
213
  graph_inputs=self.graph_inputs,
148
214
  outputs_by_node=self.outputs_by_node,
149
215
  services=services,
@@ -151,7 +217,70 @@ class RuntimeEnv:
151
217
  clock=self.clock,
152
218
  resume_payload=resume_payload,
153
219
  should_run_fn=self.should_run_fn,
220
+ scope=node_scope,
154
221
  # Back-compat shim for old ctx.mem()
155
222
  bound_memory=BoundMemoryAdapter(mem, defaults),
156
223
  resume_router=self.resume_router,
157
224
  )
225
+
226
+ def _resolve_memory_config(self) -> tuple[str, str | None]:
227
+ """
228
+ Returns (level, custom_scope_id).
229
+
230
+ Resolution order:
231
+ 1) If this run has an agent_id, read from the agent registry meta.
232
+ 2) Else if this run has an app_id, read from the app registry meta.
233
+ 3) Else fall back to graph/graphfn meta.
234
+ 4) Defaults:
235
+ - agent/app-backed runs -> "session"
236
+ - plain graph runs -> "run"
237
+ """
238
+ registry = self.registry
239
+ level: str = "session" # safe default
240
+ custom_scope_id: str | None = None
241
+ meta: dict[str, Any] = {}
242
+
243
+ if registry:
244
+ # Prefer agent meta
245
+ if self.agent_id:
246
+ meta = (
247
+ registry.get_meta(
248
+ nspace="agent",
249
+ name=self.agent_id,
250
+ version=None,
251
+ )
252
+ or {}
253
+ )
254
+ # Then app meta
255
+ elif self.app_id:
256
+ meta = (
257
+ registry.get_meta(
258
+ nspace="app",
259
+ name=self.app_id,
260
+ version=None,
261
+ )
262
+ or {}
263
+ )
264
+ # Finally, bare graph meta (graphfn or taskgraph)
265
+ elif self.graph_id:
266
+ meta = (
267
+ registry.get_meta("graphfn", self.graph_id, None)
268
+ or registry.get_meta("graph", self.graph_id, None)
269
+ or {}
270
+ )
271
+
272
+ if meta:
273
+ # Top-level keys from as_agent/as_app extras
274
+ if "memory_level" in meta:
275
+ level = meta["memory_level"]
276
+ else:
277
+ # Fallback by kind if not explicitly set
278
+ kind = meta.get("kind")
279
+ level = "session" if kind == "agent" else "run"
280
+
281
+ custom_scope_id = meta.get("memory_scope")
282
+ else:
283
+ # If we have an agent_id but no meta, still bias to session-level
284
+ level = "session" if self.agent_id else "run"
285
+
286
+ return level, custom_scope_id
@@ -0,0 +1,71 @@
1
+ from contextvars import ContextVar
2
+
3
+ from aethergraph.contracts.services.metering import MeteringService
4
+ from aethergraph.services.metering.noop import NoopMeteringService
5
+
6
+ MeterContext = dict[str, str | None]
7
+ current_meter_context: ContextVar[MeterContext] = ContextVar("ag_meter_context", default={})
8
+
9
+
10
+ # Process-wide default (can be replaced during app startup)
11
+ __singleton_metering: MeteringService = NoopMeteringService()
12
+
13
+ # Optional per-context override
14
+ _current_metering: ContextVar[MeteringService | None] = ContextVar("ag_metering", default=None)
15
+
16
+
17
+ def install_global_metering(svc: MeteringService) -> None:
18
+ """
19
+ Called at server startup to install the real metering service.
20
+
21
+ E.g. in create_app():
22
+ install_global_metering(EventLogMeteringService(meter_store))
23
+ """
24
+ global __singleton_metering
25
+ __singleton_metering = svc
26
+
27
+
28
+ def set_current_metering(svc: MeteringService) -> None:
29
+ """
30
+ Override the metering service for the current context (tests, special scopes).
31
+ """
32
+ _current_metering.set(svc)
33
+
34
+
35
+ def global_metering() -> MeteringService:
36
+ """
37
+ Return the process-wide singleton (usually a real service after startup,
38
+ or NoopMeteringService in CLI/tests).
39
+ """
40
+ return __singleton_metering
41
+
42
+
43
+ def current_metering() -> MeteringService:
44
+ """
45
+ Get the current metering service.
46
+
47
+ Priority:
48
+ 1) Container services (if installed) and they hold a .metering
49
+ 2) ContextVar override (set_current_metering)
50
+ 3) Global singleton (Noop by default)
51
+ """
52
+ from ..runtime.runtime_services import current_services # lazy import
53
+
54
+ # 1) Prefer container services.metering if present
55
+ try:
56
+ svc_container = current_services()
57
+ svc = getattr(svc_container, "metering", None)
58
+ # install the metering from services container
59
+ set_current_metering(svc)
60
+ if isinstance(svc, MeteringService.__constraints__): # type: ignore[attr-defined]
61
+ return svc
62
+ except Exception:
63
+ pass
64
+
65
+ # 2) ContextVar
66
+ svc = _current_metering.get()
67
+ if svc is not None:
68
+ return svc
69
+
70
+ # 3) Fallback
71
+ return __singleton_metering
@@ -2,31 +2,54 @@ from contextvars import ContextVar
2
2
 
3
3
  from aethergraph.services.registry.unified_registry import UnifiedRegistry
4
4
 
5
+ # Single process-wide registry instance
5
6
  __singleton_registry: UnifiedRegistry = UnifiedRegistry()
7
+
8
+ # Optional overrides per-context (rarely needed)
6
9
  _current_registry: ContextVar[UnifiedRegistry | None] = ContextVar("ag_registry", default=None)
7
10
 
8
11
 
9
- def set_current_registry(reg: UnifiedRegistry):
10
- """Set the current registry in contextvar."""
12
+ def global_registry() -> UnifiedRegistry:
13
+ """
14
+ Return the process-wide global registry instance.
15
+
16
+ Use this when you explicitly want the singleton, e.g. wiring into containers.
17
+ """
18
+ return __singleton_registry
19
+
20
+
21
+ def set_current_registry(reg: UnifiedRegistry) -> None:
22
+ """
23
+ Override the registry for the current context (e.g., tests or special scopes).
24
+ """
11
25
  _current_registry.set(reg)
12
26
 
13
27
 
14
28
  def current_registry() -> UnifiedRegistry:
15
- """Get the current registry from contextvar, or raise if not set."""
16
- # first try if services has a registry set
17
- from .runtime_services import current_services
29
+ """
30
+ Get the current registry.
31
+
32
+ Priority:
33
+ 1) Container services (if installed) and they hold a registry.
34
+ 2) ContextVar override (set_current_registry).
35
+ 3) Global singleton.
36
+ """
37
+ from .runtime_services import current_services # lazy import to avoid cycles
18
38
 
19
- svc = None
39
+ # 1) If services are installed and have a registry, prefer that
20
40
  try:
21
- # get current services and registry from there
22
41
  svc = current_services()
23
- if hasattr(svc, "registry") and svc.registry is not None:
24
- return svc.registry
42
+ reg = getattr(svc, "registry", None)
43
+ if isinstance(reg, UnifiedRegistry):
44
+ return reg
25
45
  except Exception:
46
+ # services not installed or not accessible in this context
26
47
  pass
27
48
 
28
- # otherwise use contextvar
49
+ # 2) ContextVar
29
50
  reg = _current_registry.get()
30
- if reg is None:
31
- return __singleton_registry # fallback to singleton if not set in local context
32
- return reg
51
+ if reg is not None:
52
+ return reg
53
+
54
+ # 3) Fallback to singleton
55
+ return __singleton_registry