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
@@ -23,10 +23,34 @@ __all__ = [
23
23
 
24
24
  class _ServiceHandle:
25
25
  """
26
- A callable, transparent handle around a bound service.
27
- - Attribute access delegates to the underlying service.
28
- - Calling with no args returns the service (ergonomic parity with built-ins).
29
- - Calling with args forwards to service.__call__ if present.
26
+ This class provides ergonomic access to a service instance, allowing attribute access,
27
+ direct retrieval, and forwarding of calls if the underlying service is callable.
28
+ Examples:
29
+ Accessing service attributes:
30
+ ```python
31
+ handle = _ServiceHandle("my_service", service_instance)
32
+ value = handle.some_attribute
33
+ ```
34
+ Retrieving the service instance:
35
+ ```python
36
+ svc = handle()
37
+ ```
38
+ Forwarding calls to a callable service:
39
+ ```python
40
+ result = handle(arg1, arg2)
41
+ ```
42
+ Args:
43
+ name: The name of the service for identification and error reporting.
44
+ bound_service: The actual service instance to be wrapped.
45
+ Attribute Access:
46
+ All attribute lookups are delegated to the underlying service instance.
47
+ Calling:
48
+ - If called with no arguments, returns the underlying service instance.
49
+ - If called with arguments and the service is callable, forwards the call.
50
+ - Raises TypeError if the service is not callable and called with arguments.
51
+ Returns:
52
+ The result of the underlying service's __call__ method, or the service instance itself
53
+ if called with no arguments.
30
54
  """
31
55
 
32
56
  __slots__ = ("_name", "_svc")
@@ -7,6 +7,9 @@ from datetime import datetime
7
7
  import importlib
8
8
  from typing import TYPE_CHECKING, Any
9
9
 
10
+ from aethergraph.api.v1.deps import RequestIdentity
11
+ from aethergraph.services.scope.scope import Scope
12
+
10
13
  if TYPE_CHECKING:
11
14
  from aethergraph.core.graph.task_node import TaskNodeRuntime
12
15
 
@@ -24,6 +27,10 @@ from .node_services import NodeServices
24
27
  class ExecutionContext:
25
28
  run_id: str
26
29
  graph_id: str | None
30
+ session_id: str | None
31
+ agent_id: str | None
32
+ app_id: str | None
33
+ identity: RequestIdentity | None
27
34
  graph_inputs: dict[str, Any]
28
35
  outputs_by_node: dict[str, dict[str, Any]]
29
36
  services: NodeServices
@@ -32,7 +39,7 @@ class ExecutionContext:
32
39
  resume_payload: dict[str, Any] | None = None
33
40
  should_run_fn: Callable[[], bool] | None = None
34
41
  resume_router: ResumeRouter | None = None # ResumeRouter
35
-
42
+ scope: Scope | None = None # Node Scope
36
43
  # Back-compat shim
37
44
  bound_memory: BoundMemoryAdapter | None = None
38
45
 
@@ -40,27 +47,18 @@ class ExecutionContext:
40
47
  return NodeContext(
41
48
  run_id=self.run_id,
42
49
  graph_id=self.graph_id or "",
50
+ session_id=self.session_id,
43
51
  node_id=node.node_id,
44
52
  services=self.services,
53
+ identity=self.identity,
45
54
  resume_payload=self.resume_payload,
55
+ scope=self.scope,
56
+ agent_id=self.agent_id,
57
+ app_id=self.app_id,
46
58
  # back-compat for old ctx.mem()
47
59
  bound_memory=self.bound_memory,
48
60
  )
49
61
 
50
- # def as_node_context(self, ad) -> "NodeContext":
51
- # """ Create a NodeContext representing this execution context itself as a node.
52
- # Useful for ad-hoc contexts that don't have real nodes.
53
- # """
54
- # return NodeContext(
55
- # run_id=self.run_id,
56
- # graph_id=self.graph_id or "",
57
- # node_id="ad_",
58
- # services=self.services,
59
- # resume_payload=self.resume_payload,
60
- # # back-compat for old ctx.mem()
61
- # bound_memory=self.bound_memory,
62
- # )
63
-
64
62
  # ----- helpers used by step forward() -----
65
63
  def now(self) -> datetime:
66
64
  return self.clock.now()
@@ -5,10 +5,12 @@ import threading
5
5
  from typing import Any
6
6
  import uuid
7
7
 
8
+ from aethergraph.api.v1.deps import RequestIdentity
8
9
  from aethergraph.contracts.errors.errors import GraphHasPendingWaits
9
10
  from aethergraph.contracts.services.state_stores import GraphSnapshot
11
+ from aethergraph.core.graph.task_graph import TaskGraph
10
12
  from aethergraph.core.runtime.recovery import hash_spec, recover_graph_run
11
- from aethergraph.services.container.default_container import build_default_container
13
+ from aethergraph.services.container.default_container import build_default_container # adjust path
12
14
  from aethergraph.services.state_stores.graph_observer import PersistenceObserver
13
15
  from aethergraph.services.state_stores.resume_policy import (
14
16
  assert_snapshot_json_only,
@@ -20,6 +22,7 @@ from ..execution.retry_policy import RetryPolicy
20
22
  from ..graph.graph_fn import GraphFunction
21
23
  from ..graph.graph_refs import resolve_any as _resolve_any
22
24
  from ..runtime.runtime_env import RuntimeEnv
25
+ from ..runtime.runtime_metering import current_meter_context
23
26
  from ..runtime.runtime_services import ensure_services_installed
24
27
  from .run_registration import RunRegistrationGuard
25
28
 
@@ -51,7 +54,7 @@ async def _attach_persistence(graph, env, spec, snapshot_every=1) -> Persistence
51
54
 
52
55
 
53
56
  async def _build_env(
54
- owner, inputs: dict[str, Any], **rt_overrides
57
+ owner, inputs: dict[str, Any], identity: RequestIdentity | None = None, **rt_overrides
55
58
  ) -> tuple[RuntimeEnv, RetryPolicy, int]:
56
59
  container = _get_container()
57
60
  # apply optional overrides onto the container instance
@@ -60,12 +63,36 @@ async def _build_env(
60
63
  setattr(container, k, v)
61
64
 
62
65
  run_id = rt_overrides.get("run_id") or f"run-{uuid.uuid4().hex[:8]}"
66
+ session_id = rt_overrides.get("session_id")
67
+ graph_id = getattr(owner, "graph_id", None) or getattr(owner, "name", None)
68
+
69
+ # Prefer runtime overrides (from RunRecord) over static graph metadata -- UI provenance
70
+ agent_id = (
71
+ rt_overrides.get("agent_id")
72
+ or getattr(owner, "agent_id", None)
73
+ or getattr(getattr(owner, "spec", None) or {}, "agent_id", None)
74
+ or (getattr(owner, "spec", None) or {}).get("agent_id") # for dict-like spec
75
+ )
76
+
77
+ app_id = (
78
+ rt_overrides.get("app_id")
79
+ or getattr(owner, "app_id", None)
80
+ or getattr(getattr(owner, "spec", None) or {}, "app_id", None)
81
+ or (getattr(owner, "spec", None) or {}).get("app_id")
82
+ )
83
+
63
84
  env = RuntimeEnv(
64
85
  run_id=run_id,
86
+ graph_id=graph_id,
87
+ session_id=session_id,
88
+ identity=identity,
65
89
  graph_inputs=inputs,
66
90
  outputs_by_node={},
67
91
  container=container,
92
+ agent_id=agent_id,
93
+ app_id=app_id,
68
94
  )
95
+
69
96
  retry = rt_overrides.get("retry") or RetryPolicy()
70
97
  max_conc = rt_overrides.get("max_concurrency", getattr(owner, "max_concurrency", 4))
71
98
  return env, retry, max_conc
@@ -139,7 +166,7 @@ def _resolve_graph_outputs(
139
166
  continuations=continuations,
140
167
  ) from e
141
168
 
142
- return next(iter(result.values())) if len(result) == 1 else result
169
+ return result
143
170
 
144
171
 
145
172
  def _resolve_graph_outputs_or_waits(graph, inputs, env, *, raise_on_waits: bool = True):
@@ -187,6 +214,7 @@ async def load_latest_snapshot_json(store, run_id: str) -> dict[str, Any] | None
187
214
  # JsonGraphStateStore serializes GraphSnapshot via snap.__dict__
188
215
  # load_latest_snapshot already returns a GraphSnapshot(**jsondict).
189
216
  # Convert back to plain JSON-ish dict:
217
+
190
218
  return {
191
219
  "run_id": snap.run_id,
192
220
  "graph_id": snap.graph_id,
@@ -196,22 +224,124 @@ async def load_latest_snapshot_json(store, run_id: str) -> dict[str, Any] | None
196
224
  }
197
225
 
198
226
 
227
+ def _register_metering_context(
228
+ env: RuntimeEnv, target: GraphFunction | TaskGraph | Any
229
+ ) -> dict[str, Any]:
230
+ """
231
+ Build a metering context dict from the RuntimeEnv.
232
+ """
233
+ run_id = env.run_id
234
+ # derive graph_id from target if possible - GraphFunction.name or TaskGraph.spec.graph_id
235
+ graph_id = getattr(target, "name", None) or getattr(
236
+ getattr(target, "spec", None), "graph_id", None
237
+ )
238
+
239
+ # user id etc through auth context if available
240
+ user_id = getattr(getattr(env, "identity", None), "user_id", None)
241
+ org_id = getattr(getattr(env, "identity", None), "org_id", None)
242
+
243
+ token = current_meter_context.set(
244
+ {
245
+ "run_id": run_id,
246
+ "graph_id": graph_id,
247
+ "user_id": user_id,
248
+ "org_id": org_id,
249
+ }
250
+ )
251
+ return token
252
+
253
+
199
254
  # ---------- public API ----------
200
- async def run_async(target, inputs: dict[str, Any] | None = None, **rt_overrides):
255
+ async def run_async(
256
+ target,
257
+ inputs: dict[str, Any] | None = None,
258
+ identity: RequestIdentity | None = None,
259
+ **rt_overrides,
260
+ ):
201
261
  """
202
- Generic async runner for TaskGraph or GraphFunction.
203
- - GraphFunction → delegates to gf.run(env=..., **inputs)
204
- - TaskGraph/builder schedules and resolves graph-level outputs
262
+ Execute a TaskGraph or GraphFunction asynchronously with optional persistence and resumability.
263
+
264
+ This method handles environment setup, cold-resume from persisted state (if available),
265
+ input validation, scheduling, and output resolution. It supports both fresh runs and
266
+ resuming incomplete runs, automatically wiring up persistence observers and enforcing
267
+ snapshot policies.
268
+
269
+ Examples:
270
+ Running a graph function:
271
+ ```python
272
+ result = await run_async(my_graph_fn, {"x": 1, "y": 2})
273
+ ```
274
+
275
+ Running a TaskGraph with custom run ID and identity:
276
+ ```python
277
+ result = await run_async(
278
+ my_task_graph,
279
+ {"input": 42},
280
+ run_id="custom-run-123",
281
+ identity=my_identity, # Only used with API requests. Ignored when running locally.
282
+ max_concurrency=8
283
+ )
284
+ ```
285
+
286
+ Args:
287
+ target: The TaskGraph, GraphFunction, or builder to execute.
288
+ inputs: Dictionary of input values for the graph.
289
+ identity: Optional RequestIdentity for user/session context.
290
+ **rt_overrides: Optional runtime overrides for environment and execution. Recognized runtime overrides include:
291
+
292
+ - run_id (str): Custom run identifier.
293
+ - session_id (str): Session identifier for grouping runs.
294
+ - agent_id (str): Agent identifier for provenance.
295
+ - app_id (str): Application identifier for provenance.
296
+ - retry (RetryPolicy): Custom retry policy.
297
+ - max_concurrency (int): Maximum number of concurrent tasks.
298
+ - Any additional container attributes supported by your environment.
299
+
300
+ Returns:
301
+ dict: The resolved outputs of the graph, or a status dict if waiting on continuations.
302
+
303
+ Raises:
304
+ GraphHasPendingWaits: If the graph is waiting on external events and outputs are not ready.
305
+ TypeError: If the target is not a valid TaskGraph or GraphFunction.
306
+
307
+ Notes:
308
+ - Speficially for GraphFunctions, you can directly use `await graph_fn(**inputs)` without needing `run_async`.
309
+ - `graph_fn` is not resumable; use TaskGraphs for persistence and recovery features.
310
+ - when using `graph` for persistence/resumability, ensure your outputs are JSON-serializable, for examples:
311
+ - primitive types (str, int, float, bool, None)
312
+ - lists/dicts of primitive types
313
+
314
+ - graph that can be resumed with JSON-serializable outputs:
315
+ ```python
316
+ @graphify(...)
317
+ def my_graph(...):
318
+ ...
319
+ return {"result": 42, "data": [1, 2, 3], "info": None} # valid JSON-serializable output
320
+ ```
321
+ - graph that cannot be resumed due to non-JSON-serializable outputs:
322
+ ```python
323
+ @graphify(...)
324
+ def my_graph(...):
325
+ ...
326
+ return {"chekpoint": torch.pt, "file": open("data.bin", "rb")} # invalid outputs for resuming (but valid for fresh runs)
327
+ ```
328
+ - Despite this, you can still use `graph` without persistence features; just avoid resuming such graphs.
329
+
205
330
  """
206
331
  inputs = inputs or {}
207
332
  # GraphFunction path
208
333
  if isinstance(target, GraphFunction):
209
- env, retry, max_conc = await _build_env(target, inputs, **rt_overrides)
210
- return await target.run(env=env, max_concurrency=max_conc, **inputs)
334
+ env, retry, max_conc = await _build_env(target, inputs, identity=identity, **rt_overrides)
335
+ token = _register_metering_context(env, target) # set metering context
336
+ try:
337
+ return await target.run(env=env, max_concurrency=max_conc, **inputs)
338
+ finally:
339
+ # reset metering context
340
+ current_meter_context.reset(token)
211
341
 
212
342
  # TaskGraph path
213
343
  graph = _materialize_task_graph(target)
214
- env, retry, max_conc = await _build_env(graph, inputs, **rt_overrides)
344
+ env, retry, max_conc = await _build_env(graph, inputs, identity=identity, **rt_overrides)
215
345
 
216
346
  # Extract spec for run/recovery ...
217
347
  spec = getattr(graph, "spec", None) or getattr(graph, "get_spec", lambda: None)()
@@ -266,33 +396,44 @@ async def run_async(target, inputs: dict[str, Any] | None = None, **rt_overrides
266
396
  )
267
397
 
268
398
  # Register for resumes and run
269
- with RunRegistrationGuard(run_id=env.run_id, scheduler=sched, container=env.container):
270
- try:
271
- await sched.run()
272
- except asyncio.CancelledError:
273
- raise
274
- finally:
275
- # FINAL SNAPSHOT on normal or cancelled exit (if store exists)
276
- if store and obs:
277
- artifacts = getattr(env.container, "artifacts", None)
278
- snap = await snapshot_from_graph(
279
- run_id=graph.state.run_id or env.run_id,
280
- graph_id=graph.graph_id,
281
- rev=graph.state.rev,
282
- spec_hash=hash_spec(spec),
283
- state_obj=graph.state,
284
- artifacts=artifacts,
285
- allow_externalize=False, # FIXME: artifact writer async loop error; set False to *avoid* writing artifacts during snapshot
286
- include_wait_spec=True,
287
- )
288
- await store.save_snapshot(snap)
289
-
290
- # Resolve graph-level outputs (will raise GraphHasPendingWaits if waits)
291
- return _resolve_graph_outputs_or_waits(graph, inputs, env, raise_on_waits=True)
399
+ token = _register_metering_context(env, target) # set metering context
400
+ try:
401
+ with RunRegistrationGuard(run_id=env.run_id, scheduler=sched, container=env.container):
402
+ try:
403
+ await sched.run()
404
+ except asyncio.CancelledError:
405
+ raise
406
+ finally:
407
+ # FINAL SNAPSHOT on normal or cancelled exit (if store exists)
408
+ if store and obs:
409
+ artifacts = getattr(env.container, "artifacts", None)
410
+ snap = await snapshot_from_graph(
411
+ run_id=graph.state.run_id or env.run_id,
412
+ graph_id=graph.graph_id,
413
+ rev=graph.state.rev,
414
+ spec_hash=hash_spec(spec),
415
+ state_obj=graph.state,
416
+ artifacts=artifacts,
417
+ allow_externalize=False, # FIXME: artifact writer async loop error; set False to *avoid* writing artifacts during snapshot
418
+ include_wait_spec=True,
419
+ )
420
+ await store.save_snapshot(snap)
421
+
422
+ # Resolve graph-level outputs (will raise if waits)
423
+ return _resolve_graph_outputs_or_waits(graph, inputs, env, raise_on_waits=True)
424
+ finally:
425
+ # reset metering context
426
+ current_meter_context.reset(token)
292
427
 
293
428
 
294
429
  async def run_or_resume_async(
295
- target, inputs: dict[str, Any], *, run_id: str | None = None, **rt_overrides
430
+ target,
431
+ inputs: dict[str, Any],
432
+ *,
433
+ run_id: str | None = None,
434
+ session_id: str | None = None,
435
+ identity: RequestIdentity | None = None,
436
+ **rt_overrides,
296
437
  ):
297
438
  """
298
439
  If state exists for run_id → cold resume, else fresh run.
@@ -300,7 +441,9 @@ async def run_or_resume_async(
300
441
  """
301
442
  if run_id is not None:
302
443
  rt_overrides = dict(rt_overrides or {}, run_id=run_id)
303
- return await run_async(target, inputs, **rt_overrides)
444
+ if session_id is not None:
445
+ rt_overrides = dict(rt_overrides or {}, session_id=session_id)
446
+ return await run_async(target, inputs, identity=identity, **rt_overrides)
304
447
 
305
448
 
306
449
  # sync adapter (optional, safe in notebooks/servers)
@@ -344,6 +487,48 @@ class _LoopThread:
344
487
  _LOOP = _LoopThread()
345
488
 
346
489
 
347
- def run(target, inputs: dict[str, Any] | None = None, **rt_overrides):
490
+ def run(
491
+ target,
492
+ inputs: dict[str, Any] | None = None,
493
+ identity: RequestIdentity | None = None,
494
+ **rt_overrides,
495
+ ):
496
+ """
497
+ Execute a target graph node synchronously with the provided inputs.
498
+
499
+ This function submits the execution of a target node (or graph) to the event loop,
500
+ allowing for asynchronous execution while providing a synchronous interface.
501
+ Runtime configuration overrides can be supplied as keyword arguments.
502
+
503
+ Examples:
504
+ Running a graph node with default inputs:
505
+ ```python
506
+ future = run(my_node)
507
+ result = future.result()
508
+ ```
509
+
510
+ Running with custom inputs and runtime overrides:
511
+ ```python
512
+ future = run(my_node, inputs={"x": 42}, timeout=10)
513
+ output = future.result()
514
+ ```
515
+
516
+ Args:
517
+ target: The graph node or callable to execute.
518
+ inputs: Optional dictionary of input values to pass to the node.
519
+ identity: Optional RequestIdentity for user/session context.
520
+ **rt_overrides: Additional keyword arguments to override runtime configuration.
521
+
522
+ Returns:
523
+ concurrent.futures.Future: A future representing the asynchronous execution of the node.
524
+
525
+ Notes:
526
+ - This function is suitable for use in synchronous contexts where asynchronous execution is desired.
527
+ - It is recommended to use asynchronous execution directly when possible for better performance and responsiveness.
528
+
529
+ Warnings:
530
+ - KeyboardInterrupt handling may not be perfect; consider using an async main function when possible.
531
+ - This function blocks the calling thread until the execution is complete.
532
+ """
348
533
  inputs = inputs or {}
349
- return _LOOP.submit(run_async(target, inputs, **rt_overrides))
534
+ return _LOOP.submit(run_async(target, inputs, identity=identity, **rt_overrides))