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
aethergraph/__init__.py CHANGED
@@ -1,8 +1,7 @@
1
- __version__ = "0.1.0a1"
1
+ __version__ = "0.1.0a2"
2
2
 
3
+ import logging
3
4
 
4
- # Server
5
- # Channel buttons
6
5
  from .contracts.services.channel import Button
7
6
 
8
7
  # Graphs
@@ -24,6 +23,8 @@ from .server.start import (
24
23
  stop_server, # stop the sidecar server
25
24
  )
26
25
 
26
+ logging.getLogger("aethergraph").addHandler(logging.NullHandler())
27
+
27
28
  __all__ = [
28
29
  # Server
29
30
  "start_server",
@@ -34,16 +35,9 @@ __all__ = [
34
35
  "graph_fn",
35
36
  "graphify",
36
37
  "TaskGraph",
37
- "RuntimeEnv",
38
38
  "NodeContext",
39
39
  # Services
40
40
  "Service",
41
41
  # Channel buttons
42
42
  "Button",
43
43
  ]
44
-
45
-
46
- # Setup a default null logger to avoid "No handler found" warnings
47
- import logging
48
-
49
- logging.getLogger("aethergraph").addHandler(logging.NullHandler())
@@ -0,0 +1,293 @@
1
+ # aethergraph/__main__.py
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import os
6
+ from pathlib import Path
7
+ import sys
8
+ import time
9
+
10
+ import uvicorn
11
+
12
+ from aethergraph.config.context import set_current_settings
13
+ from aethergraph.config.loader import load_settings
14
+ from aethergraph.server.app_factory import create_app
15
+ from aethergraph.server.loading import GraphLoader, LoadSpec
16
+ from aethergraph.server.server_state import (
17
+ get_running_url_if_any,
18
+ pick_free_port,
19
+ workspace_lock,
20
+ write_server_state,
21
+ )
22
+
23
+ """
24
+ AetherGraph CLI (Phase 1)
25
+
26
+ Goal: run the sidecar persistently as a long-lived process.
27
+
28
+ Why:
29
+ - Your workspace stores persistent data (runs/artifacts/memory/sessions).
30
+ - The server process must stay alive for the frontend/Electron to call the API repeatedly.
31
+ - When port=0 (auto free port), the actual URL changes per start.
32
+ We write workspace/.aethergraph/server.json so the UI can discover the URL without
33
+ hardcoding ports or parsing stdout.
34
+
35
+ Commands:
36
+
37
+ 1) Start the sidecar (blocking, recommended for "always-on" local server)
38
+ python -m aethergraph serve --workspace ./aethergraph_data --port 0 \
39
+ --project-root . \
40
+ --load-path ./graphs.py
41
+
42
+ Notes:
43
+ - --port 0 auto-picks a free port and prints the resulting URL.
44
+ - --load-path / --load-module imports user code BEFORE the server starts,
45
+ so decorated graphs/apps/agents appear immediately in the UI.
46
+ - --project-root is temporarily added to sys.path during loading (for local imports).
47
+ - server.json is written under the workspace for discovery.
48
+
49
+ 2) Reuse detection (avoid starting multiple servers for the same workspace)
50
+ python -m aethergraph serve --workspace ./aethergraph_data --reuse
51
+
52
+ Behavior:
53
+ - If a server for this workspace is already running, print its URL and exit 0.
54
+ - If not running, starts a new server.
55
+
56
+ Recommended desktop/Electron workflow:
57
+ - Electron chooses a workspace folder.
58
+ - Electron checks workspace/.aethergraph/server.json and tries to connect.
59
+ - If missing/dead, Electron spawns:
60
+ python -m aethergraph serve --workspace <workspace> --port 0 --load-path <graphs.py> ...
61
+ - Electron reads server.json to get the URL and connects.
62
+ """
63
+
64
+
65
+ def main(argv: list[str] | None = None) -> int:
66
+ """
67
+ Start the AetherGraph server via CLI.
68
+
69
+ This entrypoint launches the persistent sidecar server for your workspace,
70
+ enabling API access for frontend/UI clients. It supports automatic
71
+ port selection, workspace isolation, and dynamic loading of user graphs/apps.
72
+
73
+ Examples:
74
+ Basic usage with default workspace and port:
75
+ ```bash
76
+ python -m aethergraph serve --workspace # only default agents/apps show up
77
+ ```
78
+
79
+ load user graphs from a file and autoreload on changes:
80
+ ```bash
81
+ python -m aethergraph serve --load-path ./graphs.py --reload
82
+ ```
83
+
84
+ Load multiple modules and set a custom project root:
85
+ ```bash
86
+ python -m aethergraph serve --load-module mygraphs --project-root .
87
+ ```
88
+
89
+ Reuse detection (print URL if already running):
90
+ ```bash
91
+ python -m aethergraph serve --reuse
92
+ ```
93
+
94
+ Customize workspace and port:
95
+ ```bash
96
+ python -m aethergraph serve --workspace ./my_workspace --port 8000 # this will not show previous runs/artifacts unless reused
97
+ ```
98
+
99
+ Args:
100
+ argv: Optional list of CLI arguments. If None, uses sys.argv[1:].
101
+
102
+ Required keywords:
103
+ - `serve`: Command to start the AetherGraph server. If no other command is given, the server will only load default built-in agents/apps.
104
+
105
+ Optional keywords:
106
+ - `workspace`: Path to the workspace folder (default: ./aethergraph_data).
107
+ - `host`: Host address to bind (default: 127.0.0.1).
108
+ - `port`: Port to bind (default: 8745; use 0 for auto-pick).
109
+ - `log-level`: App log level (default: warning).
110
+ - `uvicorn-log-level`: Uvicorn log level (default: warning).
111
+ - `project-root`: Temporarily added to sys.path for local imports.
112
+ - `load-module`: Python module(s) to import before server starts (repeatable).
113
+ - `load-path`: Python file(s) to load before server starts (repeatable).
114
+ - `strict-load`: Raise error if graph loading fails.
115
+ - `reuse`: If server already running for workspace, print URL and exit.
116
+ - `reload`: Enable auto-reload (dev mode).
117
+
118
+ Returns:
119
+ int: Exit code (0 for success, 2 for unknown command).
120
+
121
+ Notes:
122
+ - Launching the server via CLI keeps it running persistently for API clients to connect like AetherGraph UI.
123
+ - In local mode, the server port will automatically be consistent with UI connections.
124
+ - use `--reload` for development to auto-restart on code changes. This will use uvicorn's reload feature.
125
+ - When switching ports, the UI will not show previous runs/artifacts unless the server is reused. This is
126
+ because the server URL is tied to the frontend hash. Keep the server in a same port (default 8745) for local dev.
127
+ Later the UI can support dynamic port discovery via server.json.
128
+ """
129
+ argv = argv if argv is not None else sys.argv[1:]
130
+
131
+ parser = argparse.ArgumentParser(prog="aethergraph")
132
+ sub = parser.add_subparsers(dest="cmd", required=True)
133
+
134
+ serve = sub.add_parser("serve", help="Run the AetherGraph sidecar (blocking).")
135
+ serve.add_argument("--workspace", default="./aethergraph_data")
136
+ serve.add_argument("--host", default="127.0.0.1")
137
+ serve.add_argument("--port", type=int, default=8745, help="0 = auto free port")
138
+ serve.add_argument("--log-level", default="warning")
139
+ serve.add_argument("--uvicorn-log-level", default="info")
140
+
141
+ serve.add_argument(
142
+ "--project-root",
143
+ default=".",
144
+ help="Root directory for the project. Added to sys.path while loading user graphs.",
145
+ )
146
+ serve.add_argument(
147
+ "--load-module", action="append", default=[], help="Module to import (repeatable)."
148
+ )
149
+ serve.add_argument(
150
+ "--load-path", action="append", default=[], help="Python file path to load (repeatable)."
151
+ )
152
+ serve.add_argument("--strict-load", action="store_true", help="Raise if graph loading fails.")
153
+
154
+ serve.add_argument(
155
+ "--reuse",
156
+ action="store_true",
157
+ help="If server already running for workspace, print URL and exit 0.",
158
+ )
159
+ serve.add_argument(
160
+ "--reload",
161
+ action="store_true",
162
+ help="Enable auto-reload on code changes (dev only).",
163
+ )
164
+
165
+ args = parser.parse_args(argv)
166
+ print(args)
167
+
168
+ if args.cmd == "serve":
169
+ loader = GraphLoader()
170
+
171
+ # Ensure one workspace => one server process
172
+ with workspace_lock(args.workspace):
173
+ running = get_running_url_if_any(args.workspace)
174
+ if running:
175
+ if args.reuse:
176
+ print(running)
177
+ return 0
178
+ print(f"Already running for workspace: {running}")
179
+ return 0
180
+
181
+ # Load graphs BEFORE app starts
182
+ project_root = args.project_root
183
+ modules = list(args.load_module or [])
184
+ paths = list(args.load_path or [])
185
+ spec = LoadSpec(
186
+ modules=list(args.load_module or []),
187
+ paths=list(args.load_path or []),
188
+ project_root=args.project_root,
189
+ strict=bool(args.strict_load),
190
+ )
191
+
192
+ # Export them to environment so the worker factory can read them
193
+ os.environ["AETHERGRAPH_WORKSPACE"] = args.workspace
194
+ os.environ["AETHERGRAPH_PROJECT_ROOT"] = str(project_root)
195
+ os.environ["AETHERGRAPH_LOAD_MODULES"] = ",".join(modules)
196
+ os.environ["AETHERGRAPH_LOAD_PATHS"] = os.pathsep.join(paths)
197
+ os.environ["AETHERGRAPH_STRICT_LOAD"] = "1" if args.strict_load else "0"
198
+ os.environ["AETHERGRAPH_LOG_LEVEL"] = args.log_level
199
+
200
+ print("=" * 50)
201
+ print("🔄 Loading graphs and agents...")
202
+ if spec.modules or spec.paths:
203
+ print(
204
+ "➕ Importing modules:",
205
+ spec.modules,
206
+ "and paths:",
207
+ spec.paths,
208
+ "at project root:",
209
+ spec.project_root,
210
+ )
211
+ report = loader.load(spec)
212
+ # Optional: print load errors but still continue if not strict
213
+ if report.errors and not args.strict_load:
214
+ for e in report.errors:
215
+ print(f"⚠️ [load error] {e.source}: {e.error}")
216
+ print(" (continuing despite load error; use --strict-load to fail)")
217
+ if e.traceback:
218
+ print(e.traceback)
219
+ print("✅ Graph/agents loading complete.")
220
+ print("=" * 50)
221
+
222
+ cfg = load_settings()
223
+ set_current_settings(cfg)
224
+
225
+ app = create_app(workspace=args.workspace, cfg=cfg, log_level=args.log_level)
226
+ app.state.last_load_report = getattr(loader, "last_report", None)
227
+
228
+ port = pick_free_port(int(args.port))
229
+ url = f"http://{args.host}:{port}"
230
+
231
+ # Write discovery file while we still hold the lock
232
+ write_server_state(
233
+ args.workspace,
234
+ {
235
+ "pid": os.getpid(),
236
+ "host": args.host,
237
+ "port": port,
238
+ "url": url,
239
+ "workspace": str(Path(args.workspace).resolve()),
240
+ "started_at": time.time(),
241
+ },
242
+ )
243
+
244
+ if not args.reload:
245
+ # Run blocking server (lock released so others can read server.json)
246
+ print("\n" + "=" * 50)
247
+ # We align the labels to 18 characters (the length of the longest label)
248
+ print(f"[AetherGraph] 🚀 {'Server started at:':<18} {url}")
249
+ print(
250
+ f"[AetherGraph] 🖥️ {'UI:':<18} {url}/ui (if built)"
251
+ ) # strangly, this needs two spaces unlike the rest
252
+ print(f"[AetherGraph] 📡 {'API:':<18} {url}/api/v1/")
253
+ print(f"[AetherGraph] 📂 {'Workspace:':<18} {args.workspace}")
254
+ print("=" * 50 + "\n")
255
+ uvicorn.run(
256
+ app,
257
+ host=args.host,
258
+ port=port,
259
+ log_level=args.uvicorn_log_level,
260
+ )
261
+ return 0
262
+
263
+ # When --reload is on:
264
+ if args.reload:
265
+ print("\n" + "=" * 50)
266
+ print(f"[AetherGraph] 🚀 {'Server started at:':<18} {url}")
267
+ print(f"[AetherGraph] 🖥️ {'UI:':<18} {url}/ui (if built)")
268
+ print(f"[AetherGraph] 📡 {'API:':<18} {url}/api/v1/")
269
+ print(f"[AetherGraph] 📂 {'Workspace:':<18} {args.workspace}")
270
+ print(f"[AetherGraph] ♻️ {'Auto-reload:':<18} enabled (uvicorn)")
271
+ print("=" * 50 + "\n")
272
+
273
+ reload_dirs: list[str] = [str(project_root)]
274
+ for p in paths:
275
+ reload_dirs.append(str(Path(p).parent))
276
+
277
+ # Use import string + factory=True here
278
+ uvicorn.run(
279
+ "aethergraph.server.app_factory:create_app_from_env",
280
+ host=args.host,
281
+ port=port,
282
+ log_level=args.uvicorn_log_level,
283
+ reload=True,
284
+ reload_dirs=reload_dirs,
285
+ factory=True,
286
+ )
287
+ return 0
288
+
289
+ return 2
290
+
291
+
292
+ if __name__ == "__main__":
293
+ raise SystemExit(main())
File without changes
@@ -0,0 +1,46 @@
1
+ # aethergraph/api/v1/agents.py
2
+
3
+ from typing import Annotated
4
+
5
+ from fastapi import APIRouter, Depends, HTTPException
6
+
7
+ from aethergraph.api.v1.deps import RequestIdentity, get_identity
8
+ from aethergraph.api.v1.schemas import AgentDescriptor
9
+ from aethergraph.core.runtime.runtime_registry import current_registry
10
+
11
+ router = APIRouter(tags=["agents"])
12
+
13
+
14
+ @router.get("/agents", response_model=list[AgentDescriptor])
15
+ async def list_agents(
16
+ identity: Annotated[RequestIdentity, Depends(get_identity)] = None,
17
+ ) -> list[AgentDescriptor]:
18
+ """
19
+ List all registered agents.
20
+
21
+ These come from `as_agent={...}` (or legacy `agent="..."`) in your decorators.
22
+ """
23
+ reg = current_registry()
24
+ if reg is None:
25
+ raise HTTPException(status_code=500, detail="Registry not available")
26
+
27
+ entries = reg.list_agents() # {'agent:designer': '0.1.0', ...}
28
+ out: list[AgentDescriptor] = []
29
+
30
+ for ref, _version in entries.items():
31
+ try:
32
+ _, name = ref.split(":", 1)
33
+ except ValueError:
34
+ continue
35
+
36
+ meta = reg.get_meta(nspace="agent", name=name) or {}
37
+ agent_id = meta.get("id", name)
38
+
39
+ out.append(
40
+ AgentDescriptor(
41
+ id=agent_id,
42
+ meta=meta,
43
+ )
44
+ )
45
+
46
+ return out
@@ -0,0 +1,70 @@
1
+ # aethergraph/api/v1/apps.py
2
+
3
+ from typing import Annotated
4
+
5
+ from fastapi import APIRouter, Depends, HTTPException
6
+
7
+ from aethergraph.api.v1.deps import RequestIdentity, get_identity
8
+ from aethergraph.api.v1.schemas import AppDescriptor
9
+ from aethergraph.core.runtime.runtime_registry import current_registry
10
+
11
+ router = APIRouter(tags=["apps"])
12
+
13
+
14
+ @router.get("/apps", response_model=list[AppDescriptor])
15
+ async def list_apps(
16
+ identity: Annotated[RequestIdentity, Depends(get_identity)] = None,
17
+ ) -> list[AppDescriptor]:
18
+ """
19
+ List all registered apps.
20
+
21
+ Each app is a graph (or graphfn) that has been decorated with `as_app={...}`.
22
+ """
23
+ reg = current_registry()
24
+ if reg is None:
25
+ raise HTTPException(status_code=500, detail="Registry not available")
26
+
27
+ # {'app:metalens': '0.1.0', ...}
28
+ entries = reg.list_apps()
29
+ out: list[AppDescriptor] = []
30
+
31
+ for ref, _version in entries.items():
32
+ # ref is "app:<name>"
33
+ try:
34
+ _, name = ref.split(":", 1)
35
+ except ValueError:
36
+ # Defensive: ignore malformed keys
37
+ continue
38
+
39
+ meta = reg.get_meta(nspace="app", name=name) or {}
40
+ app_id = meta.get("id", name)
41
+ graph_id = meta.get("graph_id", name)
42
+
43
+ out.append(
44
+ AppDescriptor(
45
+ id=app_id,
46
+ graph_id=graph_id,
47
+ meta=meta,
48
+ )
49
+ )
50
+
51
+ return out
52
+
53
+
54
+ @router.get("/apps/{app_id}", response_model=AppDescriptor)
55
+ async def get_app(
56
+ app_id: str,
57
+ identity: Annotated[RequestIdentity, Depends(get_identity)] = None,
58
+ ) -> AppDescriptor:
59
+ reg = current_registry()
60
+ if reg is None:
61
+ raise HTTPException(status_code=500, detail="Registry not available")
62
+
63
+ # Resolve by app id (we store app_id as the registry `name`)
64
+ meta = reg.get_meta(nspace="app", name=app_id)
65
+ if not meta:
66
+ raise HTTPException(status_code=404, detail=f"App not found: {app_id}")
67
+
68
+ graph_id = meta.get("graph_id", meta.get("backing", {}).get("name", app_id))
69
+
70
+ return AppDescriptor(id=meta.get("id", app_id), graph_id=graph_id, meta=meta)