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
File without changes
@@ -22,8 +22,12 @@ def current_builder() -> GraphBuilder | None:
22
22
  class GraphBuilder:
23
23
  _auto_counter = itertools.count(1)
24
24
 
25
- def __init__(self, *, name: str = "default_graph"):
26
- self.spec = TaskGraphSpec(graph_id=name, nodes={}, meta={})
25
+ def __init__(
26
+ self, *, name: str = "default_graph", agent_id: str | None = None, app_id: str | None = None
27
+ ):
28
+ self.spec = TaskGraphSpec(
29
+ graph_id=name, nodes={}, meta={}, agent_id=agent_id, app_id=app_id
30
+ )
27
31
  self.graph = TaskGraph(spec=self.spec)
28
32
  self.graph.ensure_inputs_node()
29
33
 
@@ -181,9 +185,9 @@ class GraphBuilder:
181
185
 
182
186
 
183
187
  @contextmanager
184
- def graph(*, name: str = "default_graph"):
188
+ def graph(*, name: str = "default_graph", agent_id: str | None = None, app_id: str | None = None):
185
189
  """Context manager that yields a GraphBuilder to build a TaskGraph."""
186
- builder = GraphBuilder(name=name)
190
+ builder = GraphBuilder(name=name, agent_id=agent_id, app_id=app_id)
187
191
  token = _GRAPH_CTX.set(builder)
188
192
  try:
189
193
  yield builder.graph
@@ -2,8 +2,13 @@ from __future__ import annotations
2
2
 
3
3
  from collections.abc import Callable
4
4
  import inspect
5
+ from typing import Any
5
6
 
6
7
  from aethergraph.core.runtime.run_registration import RunRegistrationGuard
8
+ from aethergraph.services.registry.agent_app_meta import (
9
+ build_agent_meta,
10
+ build_app_meta,
11
+ )
7
12
 
8
13
  from ..execution.retry_policy import RetryPolicy
9
14
  from ..runtime.runtime_env import RuntimeEnv
@@ -22,6 +27,8 @@ class GraphFunction:
22
27
  inputs: list[str] | None = None,
23
28
  outputs: list[str] | None = None,
24
29
  version: str = "0.1.0",
30
+ agent_id: str | None = None,
31
+ app_id: str | None = None,
25
32
  ):
26
33
  self.graph_id = name
27
34
  self.name = name
@@ -33,6 +40,8 @@ class GraphFunction:
33
40
  self.last_graph = None
34
41
  self.last_context = None
35
42
  self.last_memory_snapshot = None
43
+ self.agent_id = agent_id
44
+ self.app_id = app_id
36
45
 
37
46
  async def run(
38
47
  self,
@@ -62,7 +71,7 @@ class GraphFunction:
62
71
  )
63
72
  node_ctx = runtime_ctx.create_node_context(node=node_spec)
64
73
 
65
- with graph(name=self.graph_id) as G:
74
+ with graph(name=self.graph_id, agent_id=self.agent_id, app_id=self.app_id) as G:
66
75
  interp = Interpreter(G, env, retry=retry, max_concurrency=max_concurrency)
67
76
  run_id = env.run_id
68
77
 
@@ -192,28 +201,160 @@ def graph_fn(
192
201
  inputs: list[str] | None = None,
193
202
  outputs: list[str] | None = None,
194
203
  version: str = "0.1.0",
195
- agent: str | None = None, # if agent is set, register this graph fn as an agent with given name
204
+ *,
205
+ entrypoint: bool = False,
206
+ flow_id: str | None = None,
207
+ tags: list[str] | None = None,
208
+ as_agent: dict[str, Any] | None = None,
209
+ as_app: dict[str, Any] | None = None,
196
210
  ) -> Callable[[Callable], GraphFunction]:
197
- """Decorator to define a graph function."""
211
+ """
212
+ Decorator to define a graph function and optionally register it as an agent or app.
213
+
214
+ This decorator wraps a Python function as a `GraphFunction`, enabling it to be executed
215
+ as a node-based graph with runtime context, retry policy, and concurrency controls.
216
+ It also supports rich metadata registration for agent and app discovery.
198
217
 
199
- def decorator(fn: Callable):
200
- gf = GraphFunction(name=name, fn=fn, inputs=inputs, outputs=outputs, version=version)
201
- # Register in registry if given
218
+ Examples:
219
+ Basic usage:
220
+ ```python
221
+ @graph_fn(
222
+ name="add_numbers",
223
+ inputs=["a", "b"],
224
+ outputs=["sum"],
225
+ )
226
+ async def add_numbers(a: int, b: int):
227
+ return {"sum": a + b}
228
+ ```
229
+
230
+ Registering as an agent with metadata:
231
+ ```python
232
+ @graph_fn(
233
+ name="chat_agent",
234
+ inputs=["message", "files", "context_refs", "session_id", "user_meta"],
235
+ outputs=["response"],
236
+ as_agent={
237
+ "id": "chatbot",
238
+ "title": "Chat Agent",
239
+ "description": "Conversational AI agent.",
240
+ "mode": "chat_v1",
241
+ "icon": "chat",
242
+ "tags": ["chat", "nlp"],
243
+ },
244
+ )
245
+ async def chat_agent(...):
246
+ ...
247
+ ```
248
+
249
+ Registering as an app:
250
+ ```python
251
+ @graph_fn(
252
+ name="summarizer",
253
+ inputs=[],
254
+ outputs=["summary"],
255
+ as_app={
256
+ "id": "summarizer-app",
257
+ "name": "Text Summarizer",
258
+ "description": "Summarizes input text.",
259
+ "category": "Productivity",
260
+ "tags": ["nlp", "summary"],
261
+ },
262
+ )
263
+ async def summarizer():
264
+ ...
265
+ ```
266
+
267
+ Args:
268
+ name: Unique name for the graph function.
269
+ inputs: List of input parameter names. If `as_agent` is provided with `mode="chat_v1"`,
270
+ this must match `["message", "files", "context_refs", "session_id", "user_meta"]`.
271
+ outputs: List of output keys returned by the function.
272
+ version: Version string for the graph function (default: "0.1.0").
273
+ entrypoint: If True, marks this graph as the main entrypoint for a flow. [Currently unused]
274
+ flow_id: Optional flow identifier for grouping related graphs.
275
+ tags: List of string tags for discovery and categorization.
276
+ as_agent: Optional dictionary defining agent metadata. Used when running through Aethergraph UI. See additional information below.
277
+ as_app: Optional dictionary defining app metadata. Used when running through Aethergraph UI. See additional information below.
278
+
279
+ Returns:
280
+ Callable: A decorator that wraps the function as a `GraphFunction` and registers it
281
+ in the runtime registry, with agent/app metadata if provided.
282
+
283
+ Notes:
284
+ - as_agent and as_app are not needed to define a graph; they are only for registration purposes for use in Aethergraph UI.
285
+ - When registering as an agent, the `as_agent` dictionary should include at least an "id" key.
286
+ - When registering as an app, the `as_app` dictionary should include at least an "id" key.
287
+ - The decorated function can be either synchronous or asynchronous.
288
+ - Fields `inputs` and `outputs` are can be inferred from the function signature if not explicitly provided, but it's recommended to declare them for clarity.
289
+ """
290
+
291
+ def decorator(fn: Callable) -> GraphFunction:
292
+ agent_id = as_agent.get("id") if as_agent else None
293
+ app_id = as_app.get("id") if as_app else None
294
+ gf = GraphFunction(
295
+ name=name,
296
+ fn=fn,
297
+ inputs=inputs,
298
+ outputs=outputs,
299
+ version=version,
300
+ agent_id=agent_id,
301
+ app_id=app_id,
302
+ )
202
303
  registry = current_registry()
203
304
 
204
- if registry is not None:
305
+ if registry is None:
306
+ # no registry available, just return the graph function
307
+ return gf
308
+
309
+ base_tags = tags or []
310
+ graph_meta: dict[str, Any] = {
311
+ "kind": "graphfn",
312
+ "entrypoint": entrypoint,
313
+ "flow_id": flow_id or name,
314
+ "tags": base_tags,
315
+ }
316
+
317
+ registry.register(
318
+ nspace="graphfn",
319
+ name=name,
320
+ version=version,
321
+ obj=gf,
322
+ meta=graph_meta,
323
+ )
324
+
325
+ # Register as agent if requested
326
+ # 4Agent meta (if any)
327
+ agent_meta = build_agent_meta(
328
+ graph_name=name,
329
+ version=version,
330
+ graph_meta=graph_meta,
331
+ agent_cfg=as_agent,
332
+ )
333
+ if agent_meta is not None:
334
+ registry.register(
335
+ nspace="agent",
336
+ name=agent_meta["id"],
337
+ version=version,
338
+ obj=gf,
339
+ meta=agent_meta,
340
+ )
341
+
342
+ # 5) App meta (if any)
343
+ app_meta = build_app_meta(
344
+ graph_name=name,
345
+ version=version,
346
+ graph_meta=graph_meta,
347
+ app_cfg=as_app,
348
+ )
349
+ if app_meta is not None:
205
350
  registry.register(
206
- nspace="graphfn",
207
- name=name,
351
+ nspace="app",
352
+ name=app_meta["id"],
208
353
  version=version,
209
- obj=gf, # we register GraphFunction directly without spec -- graph function is already a runtime object
354
+ obj=gf,
355
+ meta=app_meta,
210
356
  )
211
357
 
212
- if agent:
213
- assert (
214
- registry is not None
215
- ), "No registry available to register agent, make sure to have a current_registry() set up."
216
- registry.register(nspace="agent", name=agent, version=version, obj=gf)
217
358
  return gf
218
359
 
219
360
  return decorator
@@ -14,6 +14,8 @@ class TaskGraphSpec:
14
14
  io: IOSpec = field(default_factory=IOSpec) # inputs/outputs
15
15
  bindings: IOBindings | None = None # input/output bindings
16
16
  meta: dict[str, Any] = field(default_factory=dict) # additional metadata
17
+ agent_id: str | None = None # for agent-invoked runs
18
+ app_id: str | None = None # for app-invoked runs
17
19
 
18
20
  def canonical(self) -> str:
19
21
  return f"graph:{self.graph_id}@{self.version}"
@@ -37,6 +39,12 @@ class TaskGraphSpec:
37
39
  f"outputs: {_fmt_outputs_map(self.outputs)}",
38
40
  ]
39
41
 
42
+ def __items__(self, key: str) -> Any:
43
+ return getattr(self, key)
44
+
45
+ def get(self, key: str, default: Any = None) -> Any:
46
+ return getattr(self, key, default)
47
+
40
48
 
41
49
  @dataclass
42
50
  class GraphView:
@@ -1,29 +1,103 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import inspect
4
+ from typing import Any
5
+
6
+ from aethergraph.services.registry.agent_app_meta import build_agent_meta, build_app_meta
4
7
 
5
8
  from ..runtime.runtime_registry import current_registry
6
9
  from .task_graph import TaskGraph
7
10
 
8
11
 
9
12
  def graphify(
10
- *, name="default_graph", inputs=(), outputs=None, version="0.1.0", agent: str | None = None
13
+ name="default_graph",
14
+ inputs=(),
15
+ outputs=None,
16
+ version="0.1.0",
17
+ *,
18
+ entrypoint: bool = False,
19
+ flow_id: str | None = None,
20
+ tags: list[str] | None = None,
21
+ as_agent: dict[str, Any] | None = None,
22
+ as_app: dict[str, Any] | None = None,
11
23
  ):
12
24
  """
13
- Decorator that builds a TaskGraph from a function body using the builder context.
14
- The function author writes sequential code with tool calls returning NodeHandles.
15
-
16
- Usage:
17
- @graphify(name="my_graph", inputs=["input1", "input2"], outputs=["output"])
18
- def my_graph(input1, input2):
19
- # function body using graph builder API
20
- pass
21
- return {"output": some_node_handle}
22
-
23
- The decorated function returns a builder function that constructs the TaskGraph.
24
-
25
- To build the graph, call the returned function:
26
- graph_instance = my_graph.build()
25
+ Decorator to define a `TaskGraph` and optionally register it as an agent or app.
26
+
27
+ This decorator wraps a Python function as a `TaskGraph`, enabling it to be executed
28
+ as a node-based graph with runtime context, retry policy, and concurrency controls.
29
+ It also supports rich metadata registration for agent and app discovery.
30
+
31
+ Examples:
32
+ Basic usage:
33
+ ```python
34
+ @graphify(
35
+ name="add_numbers",
36
+ inputs=["a", "b"],
37
+ outputs=["sum"],
38
+ )
39
+ async def add_numbers(a: int, b: int):
40
+ return {"sum": a + b}
41
+ ```
42
+
43
+ Registering as an agent with metadata:
44
+ ```python
45
+ @graphify(
46
+ name="chat_agent",
47
+ inputs=["message", "files", "context_refs", "session_id", "user_meta"],
48
+ outputs=["response"],
49
+ as_agent={
50
+ "id": "chatbot",
51
+ "title": "Chat Agent",
52
+ "description": "Conversational AI agent.",
53
+ "mode": "chat_v1",
54
+ "icon": "chat",
55
+ "tags": ["chat", "nlp"],
56
+ },
57
+ )
58
+ async def chat_agent(...):
59
+ ...
60
+ ```
61
+
62
+ Registering as an app:
63
+ ```python
64
+ @graphify(
65
+ name="summarizer",
66
+ inputs=[],
67
+ outputs=["summary"],
68
+ as_app={
69
+ "id": "summarizer-app",
70
+ "name": "Text Summarizer",
71
+ "description": "Summarizes input text.",
72
+ "category": "Productivity",
73
+ "tags": ["nlp", "summary"],
74
+ },
75
+ )
76
+ async def summarizer():
77
+ ...
78
+ ```
79
+
80
+ Args:
81
+ name: Unique name for the graph function.
82
+ inputs: List of input parameter names. If `as_agent` is provided with `mode="chat_v1"`,
83
+ this must match `["message", "files", "context_refs", "session_id", "user_meta"]`.
84
+ outputs: List of output keys returned by the function.
85
+ version: Version string for the graph function (default: "0.1.0").
86
+ entrypoint: If True, marks this graph as the main entrypoint for a flow. [Currently unused]
87
+ flow_id: Optional flow identifier for grouping related graphs.
88
+ tags: List of string tags for discovery and categorization.
89
+ as_agent: Optional dictionary defining agent metadata. Used when running through Aethergraph UI. See additional information below.
90
+ as_app: Optional dictionary defining app metadata. Used when running through Aethergraph UI. See additional information below.
91
+
92
+ Returns:
93
+ TaskGraph: A decorator that transforms a function into a TaskGraph with the specified configuration.
94
+
95
+ Notes:
96
+ - as_agent and as_app are not needed to define a graph; they are only for registration purposes for use in Aethergraph UI.
97
+ - When registering as an agent, the `as_agent` dictionary should include at least an "id" key.
98
+ - When registering as an app, the `as_app` dictionary should include at least an "id" key.
99
+ - The decorated function is a sync function (generate the TaskGraph), despite the underlying `@tool` can be async.
100
+ - Fields `inputs` and `outputs` are can be inferred from the function signature if not explicitly provided, but it's recommended to declare them for clarity.
27
101
  """
28
102
 
29
103
  def _wrap(fn):
@@ -41,7 +115,10 @@ def graphify(
41
115
  from .graph_builder import graph
42
116
  from .graph_refs import arg
43
117
 
44
- with graph(name=name) as g:
118
+ agent_id = as_agent.get("id") if as_agent else None
119
+ app_id = as_app.get("id") if as_app else None
120
+
121
+ with graph(name=name, agent_id=agent_id, app_id=app_id) as g:
45
122
  # declarations unchanged...
46
123
  if isinstance(inputs, dict):
47
124
  g.declare_inputs(required=[], optional=inputs)
@@ -52,7 +129,7 @@ def graphify(
52
129
  injected_kwargs = {p: arg(p) for p in overlap}
53
130
 
54
131
  # Run user body
55
- ret = fn(**injected_kwargs) # ← key line
132
+ ret = fn(**injected_kwargs)
56
133
 
57
134
  # expose logic (fixed typo + single-output collapse)
58
135
  def _is_ref(x):
@@ -112,16 +189,58 @@ def graphify(
112
189
  _build.io = _io
113
190
 
114
191
  # ---- Register graph + optional agent ----
115
- hub = current_registry()
116
- if hub is not None:
117
- # Prefer registering the FACTORY, not a single built instance
118
- # fallback: register a concrete instance now
119
- hub.register(nspace="graph", name=name, version=version, obj=_build())
120
-
121
- if agent:
122
- # we will have agent API later, now just register a graph as agent
123
- agent_id = agent
124
- hub.register(nspace="agent", name=agent_id, version=version, obj=_build())
192
+
193
+ registry = current_registry()
194
+ if registry is None:
195
+ return _build
196
+
197
+ base_tags = tags or []
198
+ graph_meta: dict[str, Any] = {
199
+ "kind": "graph",
200
+ "entrypoint": entrypoint,
201
+ "flow_id": flow_id or name,
202
+ "tags": base_tags,
203
+ }
204
+
205
+ registry.register(
206
+ nspace="graph",
207
+ name=name,
208
+ version=version,
209
+ obj=_build(),
210
+ meta=graph_meta,
211
+ )
212
+
213
+ # Agent meta (if any)
214
+ agent_meta = build_agent_meta(
215
+ graph_name=name,
216
+ version=version,
217
+ graph_meta=graph_meta,
218
+ agent_cfg=as_agent,
219
+ )
220
+ if agent_meta is not None:
221
+ registry.register(
222
+ nspace="agent",
223
+ name=agent_meta["id"],
224
+ version=version,
225
+ obj=_build(),
226
+ meta=agent_meta,
227
+ )
228
+
229
+ # App meta (if any)
230
+ app_meta = build_app_meta(
231
+ graph_name=name,
232
+ version=version,
233
+ graph_meta=graph_meta,
234
+ app_cfg=as_app,
235
+ )
236
+ if app_meta is not None:
237
+ registry.register(
238
+ nspace="app",
239
+ name=app_meta["id"],
240
+ version=version,
241
+ obj=_build(),
242
+ meta=app_meta,
243
+ )
125
244
 
126
245
  return _build
127
246
 
@@ -39,8 +39,6 @@ class TaskNodeSpec:
39
39
  condition: bool | dict[str, Any] | callable[[dict[str, Any]], bool] = True
40
40
 
41
41
  metadata: dict[str, Any] = field(default_factory=dict)
42
- reads: set[str] = field(default_factory=set) # state keys to read
43
- writes: set[str] = field(default_factory=set) # state keys to write
44
42
 
45
43
  tool_name: str | None = None # used for logging/monitoring
46
44
  tool_version: str | None = None # used for logging/monitoring
@@ -10,6 +10,7 @@ class NodeStatus:
10
10
  DONE = "DONE"
11
11
  FAILED = "FAILED"
12
12
  SKIPPED = "SKIPPED"
13
+ CANCELLED = "CANCELLED"
13
14
  FAILED_TIMEOUT = "FAILED_TIMEOUT"
14
15
  WAITING_HUMAN = "WAITING_HUMAN"
15
16
  WAITING_ROBOT = "WAITING_ROBOT"
@@ -56,6 +57,8 @@ class TaskNodeState:
56
57
  next_wakeup_at: str | None = None # ISO timestamp
57
58
  wait_token: str | None = None # for external wait/resume with Continuation
58
59
  wait_spec: dict[str, Any] | None = None # spec for waiting (kind, channel, meta, etc.)
60
+ started_at: str | None = None # ISO timestamp
61
+ finished_at: str | None = None # ISO timestamp
59
62
 
60
63
  @property
61
64
  def output(self):
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from collections.abc import Iterable
4
4
  from dataclasses import asdict, dataclass, field, is_dataclass
5
+ import datetime
5
6
  import inspect
6
7
  from typing import Any
7
8
  import uuid
@@ -21,6 +22,10 @@ def _dataclass_to_plain(d):
21
22
  return asdict(d) if is_dataclass(d) else d
22
23
 
23
24
 
25
+ def _utc_ts() -> float:
26
+ return datetime.datetime.now(tz=datetime.timezone.utc).timestamp()
27
+
28
+
24
29
  @dataclass
25
30
  class TaskGraph:
26
31
  spec: TaskGraphSpec
@@ -412,10 +417,43 @@ class TaskGraph:
412
417
  "set_outputs() is not implemented yet. Use set_node_outputs() instead."
413
418
  )
414
419
 
420
+ # async def set_node_status(self, node_id: str, status: NodeStatus) -> None:
421
+ # state = self.state.nodes.get(node_id)
422
+ # if state.status is status:
423
+ # return
424
+ # state.status = status
425
+ # self.state.rev += 1
426
+ # await self._notify_status_change(node_id)
427
+
415
428
  async def set_node_status(self, node_id: str, status: NodeStatus) -> None:
416
429
  state = self.state.nodes.get(node_id)
417
- if state.status is status:
430
+ if state is None:
431
+ raise KeyError(f"Unknown node_id: {node_id}")
432
+
433
+ # no-op if unchanged
434
+ if state.status is status or state.status == status:
418
435
  return
436
+
437
+ # --- timestamps ---
438
+
439
+ # 1) First time we go to RUNNING → set started_at (but don't overwrite on resume)
440
+ if status == NodeStatus.RUNNING and getattr(state, "started_at", None) is None:
441
+ state.started_at = _utc_ts()
442
+
443
+ # 2) Terminal states → set finished_at if not already set
444
+ TERMINAL_STATES = {
445
+ getattr(NodeStatus, "DONE", "DONE"),
446
+ getattr(NodeStatus, "FAILED", "FAILED"),
447
+ getattr(NodeStatus, "SKIPPED", "SKIPPED"),
448
+ getattr(NodeStatus, "CANCELLED", None) or getattr(NodeStatus, "CANCELED", None),
449
+ }
450
+ # filter out any Nones in case some names don't exist
451
+ TERMINAL_STATES = {s for s in TERMINAL_STATES if s is not None}
452
+
453
+ if status in TERMINAL_STATES and getattr(state, "finished_at", None) is None:
454
+ state.finished_at = _utc_ts()
455
+
456
+ # --- actual status change + rev bump ---
419
457
  state.status = status
420
458
  self.state.rev += 1
421
459
  await self._notify_status_change(node_id)
File without changes
@@ -17,12 +17,40 @@ class _AdhocNode:
17
17
 
18
18
 
19
19
  async def build_adhoc_context(
20
- *,
21
20
  run_id: str | None = None,
22
21
  graph_id: str = "adhoc",
23
22
  node_id: str = "adhoc",
24
23
  **rt_overrides,
25
24
  ) -> ExecutionContext:
25
+ """
26
+ Build an ad-hoc execution context for running a single node outside of a scheduled graph.
27
+ This function creates a minimal runtime environment suitable for quick, one-off executions,
28
+ such as testing or interactive exploration. It generates a temporary run and graph context,
29
+ instantiates an ad-hoc node, and returns a node-specific execution context.
30
+ Examples:
31
+ Basic usage with default parameters:
32
+ ```python
33
+ node_ctx = await build_adhoc_context()
34
+ ```
35
+ Customizing the run and session IDs:
36
+ ```python
37
+ node_ctx = await build_adhoc_context(run_id="test-run", session_id="dev-session")
38
+ ```
39
+ Overriding runtime parameters:
40
+ ```python
41
+ node_ctx = await build_adhoc_context(max_concurrency=4)
42
+ ```
43
+ Args:
44
+ run_id: Optional string to uniquely identify this run. If not provided,
45
+ a random ID is generated.
46
+ session_id: Optional string to associate this context with a session.
47
+ graph_id: Identifier for the graph. Defaults to `"adhoc"`.
48
+ node_id: Identifier for the node. Defaults to `"adhoc"`.
49
+ **rt_overrides: Additional runtime overrides, such as `max_concurrency`.
50
+ Returns:
51
+ NodeExecutionContext: The execution context for the ad-hoc node, ready for use.
52
+ """
53
+
26
54
  # Owner can be anything with max_concurrency; we won't really schedule
27
55
  class _Owner:
28
56
  max_concurrency = rt_overrides.get("max_concurrency", 1)
@@ -41,15 +69,47 @@ async def build_adhoc_context(
41
69
 
42
70
  @asynccontextmanager
43
71
  async def open_session(
44
- *,
45
72
  run_id: str | None = None,
46
73
  graph_id: str = "adhoc",
47
74
  node_id: str = "adhoc",
48
75
  **rt_overrides,
49
76
  ):
50
77
  """
51
- Open an 'adhoc' context that behaves like a NodeContext, without a real graph run.
52
- Advanced / scripting use only.
78
+ Open an ad-hoc session context for advanced or scripting use.
79
+
80
+ This asynchronous context manager yields a temporary context that mimics a `NodeContext`
81
+ without requiring a real graph run. It is intended for advanced scenarios where a lightweight,
82
+ ephemeral execution environment is needed, such as scripting, testing, or prototyping.
83
+
84
+ Examples:
85
+ Basic usage to open an ad-hoc session:
86
+ ```python
87
+ async with open_session() as ctx:
88
+ # Use ctx as you would a NodeContext
89
+ ...
90
+ ```
91
+
92
+ Overriding runtime parameters:
93
+ ```python
94
+ async with open_session(graph_id="mygraph", node_id="customnode", foo="bar") as ctx:
95
+ ...
96
+ ```
97
+
98
+ Args:
99
+ run_id: Optional unique identifier for the run. If None, a random or default value is used.
100
+ graph_id: Identifier for the graph context. Defaults to "adhoc".
101
+ node_id: Identifier for the node context. Defaults to "adhoc".
102
+ **rt_overrides: Arbitrary keyword arguments to override runtime context parameters.
103
+
104
+ Yields:
105
+ NodeContext: An ad-hoc context object suitable for advanced scripting or testing.
106
+
107
+ Raises:
108
+ Any exception raised during context construction or teardown will propagate.
109
+
110
+ Note:
111
+ This context does not persist state or artifacts beyond its lifetime. Use only for
112
+ non-production, ephemeral tasks.
53
113
  """
54
114
  ctx = await build_adhoc_context(
55
115
  run_id=run_id, graph_id=graph_id, node_id=node_id, **rt_overrides