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,13 +1,26 @@
1
1
  from dataclasses import dataclass
2
2
  from datetime import timedelta
3
- from typing import Any
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from aethergraph.services.artifacts.facade import ArtifactFacade
6
+
7
+ if TYPE_CHECKING:
8
+ from aethergraph.core.runtime.run_manager import RunManager
4
9
 
5
10
  from aethergraph.contracts.services.llm import LLMClientProtocol
11
+ from aethergraph.core.runtime.run_types import (
12
+ RunImportance,
13
+ RunOrigin,
14
+ RunRecord,
15
+ RunVisibility,
16
+ )
6
17
  from aethergraph.core.runtime.runtime_services import get_ext_context_service
7
18
  from aethergraph.services.channel.session import ChannelSession
8
19
  from aethergraph.services.continuations.continuation import Continuation
9
20
  from aethergraph.services.llm.providers import Provider
10
21
  from aethergraph.services.memory.facade import MemoryFacade
22
+ from aethergraph.services.scope.scope import Scope
23
+ from aethergraph.services.viz.facade import VizFacade
11
24
 
12
25
  from .base_service import _ServiceHandle
13
26
  from .bound_memory import BoundMemoryAdapter
@@ -17,22 +30,346 @@ from .node_services import NodeServices
17
30
  @dataclass
18
31
  class NodeContext:
19
32
  run_id: str
33
+ session_id: str
20
34
  graph_id: str
21
35
  node_id: str
22
36
  services: NodeServices
37
+ identity: Any = None
23
38
  resume_payload: dict[str, Any] | None = None
39
+ scope: Scope | None = None
40
+ agent_id: str | None = None # for agent-invoked runs
41
+ app_id: str | None = None # for app-invoked runs
24
42
  bound_memory: BoundMemoryAdapter | None = None # back-compat
25
43
 
26
44
  # --- accessors (compatible names) ---
27
45
  def runtime(self) -> NodeServices:
28
46
  return self.services
29
47
 
48
+ async def spawn_run(
49
+ self,
50
+ graph_id: str,
51
+ *,
52
+ inputs: dict[str, Any],
53
+ session_id: str | None = None,
54
+ tags: list[str] | None = None,
55
+ visibility: RunVisibility | None = None,
56
+ origin: RunOrigin | None = None,
57
+ importance: RunImportance | None = None,
58
+ agent_id: str | None = None,
59
+ app_id: str | None = None,
60
+ run_id: str | None = None,
61
+ ) -> str:
62
+ """
63
+ Launch a new run from within the current node or graph context.
64
+
65
+ This method creates and schedules a new run for the specified graph, using the provided inputs and optional metadata.
66
+ It does not wait for the run to complete; instead, it returns immediately with the new run's ID.
67
+ The run is managed asynchronously in the background, and is tracked and persisted via the configured RunManager.
68
+
69
+ Examples:
70
+ Basic usage to spawn a run for a graph:
71
+ ```python
72
+ run_id = await context.spawn_run(
73
+ "my-graph-id",
74
+ inputs={"x": 1, "y": 2}
75
+ )
76
+ ```
77
+
78
+ Spawning a run with custom tags and agent context:
79
+ ```python
80
+ from aethergraph.runtime import RunVisibility
81
+ run_id = await context.spawn_run(
82
+ "my-graph-id",
83
+ inputs={"foo": "bar"},
84
+ tags=["experiment", "priority"],
85
+ agent_id="agent-123", # associate with an agent if applicable
86
+ visibility=RunVisibility.ineline, # not shown in UI
87
+ )
88
+ ```
89
+
90
+ Args:
91
+ graph_id: The unique identifier of the graph to execute. i.e. the `name` field of a registered graph.
92
+ inputs: Dictionary of input values to pass to the graph.
93
+ session_id: Optional session identifier. Defaults to the current context's session if not provided.
94
+ tags: Optional list of string tags for categorization and tracking.
95
+ visibility: Optional visibility setting for the run (e.g., public, private, normal).
96
+ origin: Optional indicator of the run's origin (e.g., agent, app). Defaults based on agent_id.
97
+ importance: Optional importance level for the run (e.g., normal, high).
98
+ agent_id: Optional agent identifier if the run is associated with an agent.
99
+ app_id: Optional application identifier if the run is associated with an app.
100
+ run_id: Optional explicit run identifier. If not provided, one is generated.
101
+
102
+ Returns:
103
+ str: The unique run_id of the newly created run.
104
+
105
+ Raises:
106
+ RuntimeError: If the RunManager service is not configured in the context.
107
+
108
+ Notes:
109
+ - The spawned run inherits the context's identity for provenance tracking.
110
+ - Metadata `tags`, `visibility`, `origin`, `importance`, `agent_id`, `app_id`, help manage and monitor the run in AG UI,
111
+ but do not affect the execution logic of the graph itself. If you are not using AG UI, these fields can be omitted.
112
+ """
113
+ rm: RunManager | None = getattr(self.services, "run_manager", None)
114
+ if rm is None:
115
+ raise RuntimeError("NodeContext.services.run_manager is not configured")
116
+ effective_session_id = session_id or self.session_id
117
+
118
+ record = await rm.submit_run(
119
+ graph_id=graph_id,
120
+ inputs=inputs,
121
+ run_id=run_id,
122
+ session_id=effective_session_id,
123
+ tags=tags,
124
+ visibility=visibility or RunVisibility.normal,
125
+ origin=origin or (RunOrigin.agent if agent_id is not None else RunOrigin.app),
126
+ importance=importance or RunImportance.normal,
127
+ agent_id=agent_id,
128
+ app_id=app_id,
129
+ identity=self.identity, # internal spawn; not coming from HTTP directly
130
+ )
131
+
132
+ return record.run_id
133
+
134
+ async def run_and_wait(
135
+ self,
136
+ graph_id: str,
137
+ *,
138
+ inputs: dict[str, Any],
139
+ session_id: str | None = None,
140
+ tags: list[str] | None = None,
141
+ visibility: RunVisibility | None = None,
142
+ origin: RunOrigin | None = None,
143
+ importance: RunImportance | None = None,
144
+ agent_id: str | None = None,
145
+ app_id: str | None = None,
146
+ run_id: str | None = None,
147
+ ) -> tuple[str, dict[str, Any] | None, bool, list[dict[str, Any]]]:
148
+ """
149
+ Run a child graph as a first-class RunManager run and wait for completion.
150
+
151
+ This method launches a new run for the specified graph, waits for it to finish,
152
+ and returns its outputs and metadata. The run is tracked and visualized in the UI,
153
+ and all status updates are persisted via the RunManager.
154
+
155
+ Examples:
156
+ Basic usage to run and wait for a graph:
157
+ ```python
158
+ run_id, outputs, has_waits, continuations = await context.run_and_wait(
159
+ "my-graph-id",
160
+ inputs={"x": 1, "y": 2}
161
+ )
162
+ ```
163
+
164
+ Running with custom tags and agent context:
165
+ ```python
166
+ run_id, outputs, has_waits, continuations = await context.run_and_wait(
167
+ "my-graph-id",
168
+ inputs={"foo": "bar"},
169
+ tags=["experiment", "priority"],
170
+ agent_id="agent-123",
171
+ visibility=RunVisibility.inline,
172
+ )
173
+ ```
174
+
175
+ Args:
176
+ graph_id: The unique identifier of the graph to execute.
177
+ inputs: Dictionary of input values to pass to the graph.
178
+ session_id: Optional session identifier. Defaults to the current context's session.
179
+ tags: Optional list of string tags for categorization and tracking.
180
+ visibility: Optional visibility setting for the run (e.g., public, private, normal).
181
+ origin: Optional indicator of the run's origin (e.g., agent, app).
182
+ importance: Optional importance level for the run (e.g., normal, high).
183
+ agent_id: Optional agent identifier if the run is associated with an agent.
184
+ app_id: Optional application identifier if the run is associated with an app.
185
+ run_id: Optional explicit run identifier. If not provided, one is generated.
186
+
187
+ Returns:
188
+ run_id (str): The unique run ID of the completed run.
189
+ outputs (dict | None): The outputs returned by the graph.
190
+ has_waits (bool): True if the run contained any wait nodes. [Not currently used]
191
+ continuations (list[dict]): List of continuation metadata, if any. [Not currently used]
192
+
193
+ Raises:
194
+ RuntimeError: If the RunManager service is not configured in the context.
195
+
196
+ Notes:
197
+ - The run is fully tracked and visualized in the AG UI.
198
+ - Use this method for orchestration patterns where you need to await child runs.
199
+ - Metadata fields help with monitoring and provenance, but do not affect graph logic.
200
+
201
+ Warning:
202
+ - This method blocks until the child run completes.
203
+ - This method will not honor the concurrency limits of the parent run, and may lead to deadlocks if the parent run is waiting on resources held by the child run.
204
+ - Avoid using this method in high-concurrency scenarios to prevent deadlocks.
205
+ For such cases, consider using `spawn_run` followed by `wait_run` instead.
206
+ """
207
+ rm: RunManager | None = getattr(self.services, "run_manager", None)
208
+ if rm is None:
209
+ raise RuntimeError("NodeContext.services.run_manager is not configured")
210
+
211
+ effective_session_id = session_id or self.session_id
212
+
213
+ record, outputs, has_waits, continuations = await rm.run_and_wait(
214
+ graph_id,
215
+ inputs=inputs,
216
+ run_id=run_id,
217
+ session_id=effective_session_id,
218
+ tags=tags,
219
+ visibility=visibility or RunVisibility.normal,
220
+ origin=origin or (RunOrigin.agent if agent_id is not None else RunOrigin.app),
221
+ importance=importance or RunImportance.normal,
222
+ agent_id=agent_id,
223
+ app_id=app_id,
224
+ identity=self.identity, # keep provenance consistent with spawn_run
225
+ count_slot=False, # nested orchestration: avoid deadlock
226
+ )
227
+
228
+ return record.run_id, outputs, has_waits, continuations
229
+
230
+ async def wait_run(
231
+ self,
232
+ run_id: str,
233
+ *,
234
+ timeout_s: float | None = None,
235
+ ) -> RunRecord:
236
+ """
237
+ Wait for a run to complete and retrieve its final record.
238
+
239
+ This method waits the RunManager for the specified run until it finishes,
240
+ then returns the completed RunRecord. Optionally, a timeout (in seconds)
241
+ can be set to limit how long to wait.
242
+
243
+ Examples:
244
+ Basic usage to wait for a run:
245
+ ```python
246
+ run_id = await context.spawn_run("my-graph-id", inputs={"x": 1})
247
+ record = await context.wait_run(run_id)
248
+ ```
249
+
250
+ Waiting with a timeout:
251
+ ```python
252
+ record = await context.wait_run(run_id, timeout_s=30)
253
+ ```
254
+
255
+ Args:
256
+ run_id: The unique identifier of the run to wait for.
257
+ timeout_s: Optional timeout in seconds. If set, the method will raise
258
+ a TimeoutError if the run does not complete in time.
259
+
260
+ Returns:
261
+ RunRecord: The final record of the completed run.
262
+
263
+ Raises:
264
+ RuntimeError: If the RunManager service is not configured in the context.
265
+ TimeoutError: If the run does not complete within the specified timeout.
266
+
267
+ Notes:
268
+ - This method is useful for orchestration patterns where you need to
269
+ synchronize on the completion of child runs.
270
+ - For high-concurrency scenarios, prefer using `spawn_run` and `wait_run`
271
+ in combination rather than `run_and_wait`.
272
+ """
273
+ rm: RunManager | None = getattr(self.services, "run_manager", None)
274
+ if rm is None:
275
+ raise RuntimeError("NodeContext.services.run_manager is not configured")
276
+ return await rm.wait_run(run_id, timeout_s=timeout_s)
277
+
278
+ async def cancel_run(self, run_id: str) -> None:
279
+ """
280
+ Cancel a scheduled or running child run by its unique ID.
281
+
282
+ This method requests cancellation of a run managed by the RunManager.
283
+ The cancellation is propagated to the run's execution context, and any
284
+ in-progress tasks will be interrupted if possible.
285
+
286
+ Examples:
287
+ Basic usage to cancel a spawned run:
288
+ ```python
289
+ run_id = await context.spawn_run("my-graph-id", inputs={"x": 1})
290
+ await context.cancel_run(run_id)
291
+ ```
292
+
293
+ Cancel a run after waiting for a condition:
294
+ ```python
295
+ if should_abort:
296
+ await context.cancel_run(run_id)
297
+ ```
298
+
299
+ Args:
300
+ run_id: The unique identifier of the run to cancel.
301
+
302
+ Returns:
303
+ None. The cancellation request is dispatched to the RunManager.
304
+
305
+ Raises:
306
+ RuntimeError: If the RunManager service is not configured in the context.
307
+
308
+ Notes:
309
+ - Cancellation is best-effort and may not immediately terminate all tasks.
310
+ - Use this method for orchestration patterns where you need to abort child runs.
311
+ - The run's status will be updated to "cancelled" in the UI and persistence layer.
312
+ """
313
+ rm: RunManager | None = getattr(self.services, "run_manager", None)
314
+ if rm is None:
315
+ raise RuntimeError("NodeContext.services.run_manager is not configured")
316
+ await rm.cancel_run(run_id)
317
+
30
318
  def logger(self):
31
319
  return self.services.logger.for_node_ctx(
32
320
  run_id=self.run_id, node_id=self.node_id, graph_id=self.graph_id
33
321
  )
34
322
 
323
+ def ui_session_channel(self) -> "ChannelSession":
324
+ """
325
+ Creates a new ChannelSession for the current node context with session key as
326
+ `ui:session/<session_id>`.
327
+
328
+ This method is a convenience helper for the AG UI to get the default session channel.
329
+
330
+ Returns:
331
+ ChannelSession: The channel session associated with the current session.
332
+ """
333
+ if not self.session_id:
334
+ raise RuntimeError("NodeContext.session_id is not set")
335
+ return ChannelSession(self, f"ui:session/{self.session_id}")
336
+
337
+ def ui_run_channel(self) -> "ChannelSession":
338
+ """
339
+ Creates a new ChannelSession for the current node context with session key as
340
+ `ui:run/<run_id>`.
341
+
342
+ This method is a convenience helper for the AG UI to get the default run channel.
343
+
344
+ Returns:
345
+ ChannelSession: The channel session associated with the current run.
346
+ """
347
+ return ChannelSession(self, f"ui:run/{self.run_id}")
348
+
35
349
  def channel(self, channel_key: str | None = None):
350
+ """
351
+ Set up a new ChannelSession for the current node context.
352
+
353
+ Args:
354
+ channel_key (str | None): An optional key to specify a particular channel.
355
+ If not provided, the default channel will be used.
356
+
357
+ Returns:
358
+ ChannelSession: An instance representing the session for the specified channel.
359
+
360
+ Notes:
361
+ Supported channel key formats include:
362
+
363
+ | Channel Type | Format Example | Notes |
364
+ |----------------------|-----------------------------------------------|---------------------------------------|
365
+ | Console | `console:stdin` | Console input/output |
366
+ | Slack | `slack:team/{team_id}:chan/{channel_id}` | Needs additional configuration |
367
+ | Telegram | `tg:chat/{chat_id}` | Needs additional configuration |
368
+ | UI Session | `ui:session/{session_id}` | Requires AG web UI |
369
+ | UI Run | `ui:run/{run_id}` | Requires AG web UI |
370
+ | Webhook | `webhook:{unique_identifier}` | For Slack, Discord, Zapier, etc. |
371
+ | File-based channel | `file:path/to/directory` | File system based channels |
372
+ """
36
373
  return ChannelSession(self, channel_key)
37
374
 
38
375
  # New way: prefer memory_facade directly
@@ -48,7 +385,7 @@ class NodeContext:
48
385
  return self.bound_memory
49
386
 
50
387
  # Artifacts / index
51
- def artifacts(self):
388
+ def artifacts(self) -> ArtifactFacade:
52
389
  return self.services.artifact_store
53
390
 
54
391
  def kv(self):
@@ -56,6 +393,11 @@ class NodeContext:
56
393
  raise RuntimeError("KV not available")
57
394
  return self.services.kv
58
395
 
396
+ def viz(self) -> VizFacade:
397
+ if not self.services.viz:
398
+ raise RuntimeError("Viz service (facade) not available")
399
+ return self.services.viz
400
+
59
401
  def llm(
60
402
  self,
61
403
  profile: str = "default",
@@ -68,9 +410,43 @@ class NodeContext:
68
410
  timeout: float | None = None,
69
411
  ) -> LLMClientProtocol:
70
412
  """
71
- Get an LLM client by profile.
72
- - If no overrides are provided, just return existing profile.
73
- - If overrides are provided, create/update that profile at runtime.
413
+ Retrieve or configure an LLM client for this context.
414
+
415
+ This method allows you to access a language model client by profile name,
416
+ or dynamically override its configuration at runtime.
417
+
418
+ Examples:
419
+ Get the default LLM client:
420
+ ```python
421
+ llm = context.llm()
422
+ response = await llm.complete("Hello, world!")
423
+ ```
424
+
425
+ Use a custom profile:
426
+ ```python
427
+ llm = context.llm(profile="my-profile")
428
+ ```
429
+
430
+ Override provider and model for a one-off call:
431
+ ```python
432
+ llm = context.llm(
433
+ provider=Provider.OpenAI,
434
+ model="gpt-4-turbo",
435
+ api_key="sk-...",
436
+ )
437
+ ```
438
+
439
+ Args:
440
+ profile: The profile name to use (default: "default"). Set up in `.env` or `register_llm_client()` method.
441
+ provider: Optionally override the provider (e.g., `Provider.OpenAI`).
442
+ model: Optionally override the model name.
443
+ base_url: Optionally override the base URL for the LLM API.
444
+ api_key: Optionally override the API key for authentication.
445
+ azure_deployment: Optionally specify an Azure deployment name.
446
+ timeout: Optionally set a request timeout (in seconds).
447
+
448
+ Returns:
449
+ LLMClientProtocol: The configured LLM client instance for this context.
74
450
  """
75
451
  svc = self.services.llm
76
452
 
@@ -96,7 +472,41 @@ class NodeContext:
96
472
 
97
473
  def llm_set_key(self, provider: str, model: str, api_key: str, profile: str = "default"):
98
474
  """
99
- Quickly configure or override the provider/key for a profile.
475
+ Quickly configure or override the LLM provider, model, and API key for a given profile.
476
+
477
+ This method allows you to update the credentials and model configuration for a specific
478
+ LLM profile at runtime. It is useful for dynamically switching providers or rotating keys
479
+ without restarting the application.
480
+
481
+ Examples:
482
+ Set the OpenAI API key for the default profile:
483
+ ```python
484
+ context.llm_set_key(
485
+ provider="openai",
486
+ model="gpt-4-turbo",
487
+ api_key="sk-...",
488
+ )
489
+ ```
490
+
491
+ Configure a custom profile for Anthropic:
492
+ ```python
493
+ context.llm_set_key(
494
+ provider="anthropic",
495
+ model="claude-3-opus",
496
+ api_key="sk-ant-...",
497
+ profile="anthropic-profile"
498
+ )
499
+ ```
500
+
501
+ Args:
502
+ provider: The LLM provider name (e.g., "openai", "anthropic").
503
+ model: The model name or identifier to use.
504
+ api_key: The API key or credential for the provider.
505
+ profile: The profile name to update (default: "default").
506
+
507
+ Returns:
508
+ None. The profile is updated in-place and will be used for subsequent calls
509
+ to `context.llm(profile=...)`.
100
510
  """
101
511
  svc = self.services.llm
102
512
  svc.set_key(provider=provider, model=model, api_key=api_key, profile=profile)
@@ -124,6 +534,34 @@ class NodeContext:
124
534
  return self.services.clock
125
535
 
126
536
  def svc(self, name: str) -> Any:
537
+ """
538
+ Retrieve and bind an external context service by name. This method is equivalent to `context.<service_name>()`.
539
+ User can use either `context.svc("service_name")` or `context.service_name()` to access the service.
540
+
541
+ This method accesses a registered external service, optionally binding it to the current
542
+ node context if the service supports context binding via a `bind` method.
543
+
544
+ Examples:
545
+ Basic usage to access a service:
546
+ ```python
547
+ db = context.svc("database")
548
+ ```
549
+
550
+ Accessing a service that requires context binding:
551
+ ```python
552
+ logger = context.svc("logger")
553
+ logger.info("Node started.")
554
+ ```
555
+
556
+ Args:
557
+ name: The unique string identifier of the external service to retrieve.
558
+
559
+ Returns:
560
+ Any: The external service instance, bound to the current context if applicable.
561
+
562
+ Raises:
563
+ KeyError: If the requested service is not registered in the external context.
564
+ """
127
565
  # generic accessor for external context services
128
566
  raw = get_ext_context_service(name)
129
567
  if raw is None:
@@ -135,6 +573,68 @@ class NodeContext:
135
573
  return raw
136
574
 
137
575
  def __getattr__(self, name: str) -> Any:
576
+ """
577
+ Retrieve and bind an external context service by name. This allows accessing services as attributes on the context object.
578
+
579
+ This method overrides attribute access to dynamically resolve external services registered in the context.
580
+ If a service with the requested name exists, it is retrieved and wrapped in a `_ServiceHandle` for ergonomic access.
581
+ The returned handle allows attribute access, direct retrieval, and call forwarding if the service is callable.
582
+
583
+ Examples:
584
+ ```python
585
+ # Retrieve a database service and run a query
586
+ db = context.database()
587
+ db.query("SELECT * FROM users")
588
+
589
+ # Access a logger service and log a message
590
+ context.logger.info("Hello from node!")
591
+
592
+ # Forward arguments to a callable service
593
+ result = context.some_tool("input text")
594
+ ```
595
+
596
+ Args:
597
+ name: The name of the service to resolve as an attribute.
598
+
599
+ Returns:
600
+ _ServiceHandle: A callable handle to the resolved service.
601
+
602
+ Raises:
603
+ AttributeError: If no service with the given name exists in the context.
604
+
605
+ Usage:
606
+ - You can access external services directly as attributes on the context object.
607
+ For example, if you have registered a service named "my_service", you can use:
608
+
609
+ ```python
610
+ # Get the service instance
611
+ svc = context.my_service()
612
+
613
+ # Call the service if it's callable
614
+ result = context.my_service(arg1, arg2)
615
+
616
+ # Access service attributes
617
+ value = context.my_service.some_attribute
618
+ ```
619
+
620
+ - In your Service, you can use `self.ctx` to access the node context if needed. For example:
621
+ ```python
622
+ class MyService:
623
+ ...
624
+ def my_method(self, ...):
625
+ context = self.ctx # Access the NodeContext
626
+ # Use context information as needed
627
+ context.channel.send("Hello from MyService!")
628
+ ```
629
+
630
+ Notes:
631
+ - If the service is not registered, an AttributeError is raised.
632
+ - If the service is callable, calling `context.service_name(args)` will forward the call.
633
+ - If you call `context.service_name()` with no arguments, you get the underlying service instance.
634
+ - Attribute access (e.g., `context.service_name.some_attr`) is delegated to the service.
635
+
636
+
637
+ """
138
638
  # Try to resolve as an external context service
139
639
  try:
140
640
  bound = self.svc(name)
@@ -185,6 +685,10 @@ class NodeContext:
185
685
  created_at=self._now(),
186
686
  attempts=attempts,
187
687
  payload=payload,
688
+ session_id=getattr(self, "session_id", None),
689
+ agent_id=getattr(self, "agent_id", None),
690
+ app_id=getattr(self, "app_id", None),
691
+ graph_id=getattr(self, "graph_id", None),
188
692
  )
189
693
  await self.services.continuation_store.save(continuation)
190
694
  return continuation
@@ -1,14 +1,19 @@
1
+ from __future__ import annotations
2
+
1
3
  from dataclasses import dataclass
2
- from typing import Any
4
+ from typing import TYPE_CHECKING, Any
3
5
 
4
- from aethergraph.contracts.services.llm import LLMClientProtocol
6
+ if TYPE_CHECKING:
7
+ from aethergraph.core.runtime.run_manager import RunManager
5
8
  from aethergraph.services.channel.channel_bus import ChannelBus
6
9
  from aethergraph.services.clock.clock import SystemClock
7
10
  from aethergraph.services.continuations.stores.fs_store import FSContinuationStore
11
+ from aethergraph.services.llm.service import LLMService
8
12
  from aethergraph.services.logger.std import StdLoggerService
9
13
  from aethergraph.services.mcp.service import MCPService
10
14
  from aethergraph.services.memory.facade import MemoryFacade
11
- from aethergraph.services.rag.facade import RAGFacade
15
+ from aethergraph.services.rag.node_rag import NodeRAG
16
+ from aethergraph.services.viz.facade import VizFacade
12
17
  from aethergraph.services.waits.wait_registry import WaitRegistry
13
18
 
14
19
 
@@ -25,6 +30,8 @@ class NodeServices:
25
30
  kv: Any | None = None
26
31
  memory: Any | None = None # MemoryFactory (for cross-session needs)
27
32
  memory_facade: MemoryFacade | None = None # bound memory for this node
28
- llm: LLMClientProtocol | None = None # LLMService
29
- rag: RAGFacade | None = None # RAGService
33
+ viz: VizFacade | None = None # VizFacade
34
+ llm: LLMService | None = None # LLMService
35
+ rag: NodeRAG | None = None # RAGService
30
36
  mcp: MCPService | None = None # MCPService
37
+ run_manager: RunManager | None = None # RunManager
@@ -19,6 +19,8 @@ def hash_spec(spec: TaskGraphSpec) -> str:
19
19
  raw = json.dumps(
20
20
  {
21
21
  "graph_id": spec.graph_id,
22
+ "agent_id": spec.agent_id or "",
23
+ "app_id": spec.app_id or "",
22
24
  "version": spec.version,
23
25
  "nodes": {
24
26
  nid: {
@@ -65,7 +67,15 @@ async def recover_graph_run(
65
67
  )
66
68
 
67
69
  # Apply snapshot state
68
- _hydrate_state_from_json(g, snap.state)
70
+ try:
71
+ _hydrate_state_from_json(g, snap.state)
72
+ except Exception as e:
73
+ import logging
74
+
75
+ logger = logging.getLogger("aethergraph.core.runtime.recovery")
76
+ logger.error(
77
+ f"[recover_graph_run] Failed to hydrate state from snapshot for run {run_id}: {e}"
78
+ )
69
79
 
70
80
  return g
71
81
 
@@ -85,6 +95,10 @@ def _hydrate_state_from_json(graph, j: dict[str, Any]) -> None:
85
95
  # Keep as-is; resume_policy already blocked non-JSON/ref earlier
86
96
  ns.outputs = outs
87
97
 
98
+ # time fields
99
+ ns.started_at = ns_json.get("started_at")
100
+ ns.finished_at = ns_json.get("finished_at")
101
+
88
102
 
89
103
  async def rearm_waits_if_needed(graph, env, *, ttl_s: int = 3600):
90
104
  store = env.container.cont_store