aethergraph 0.1.0a1__py3-none-any.whl → 0.1.0a3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. aethergraph/__init__.py +4 -10
  2. aethergraph/__main__.py +296 -0
  3. aethergraph/api/v1/__init__.py +0 -0
  4. aethergraph/api/v1/agents.py +46 -0
  5. aethergraph/api/v1/apps.py +70 -0
  6. aethergraph/api/v1/artifacts.py +415 -0
  7. aethergraph/api/v1/channels.py +89 -0
  8. aethergraph/api/v1/deps.py +168 -0
  9. aethergraph/api/v1/graphs.py +259 -0
  10. aethergraph/api/v1/identity.py +25 -0
  11. aethergraph/api/v1/memory.py +353 -0
  12. aethergraph/api/v1/misc.py +47 -0
  13. aethergraph/api/v1/pagination.py +29 -0
  14. aethergraph/api/v1/runs.py +568 -0
  15. aethergraph/api/v1/schemas.py +535 -0
  16. aethergraph/api/v1/session.py +323 -0
  17. aethergraph/api/v1/stats.py +201 -0
  18. aethergraph/api/v1/viz.py +152 -0
  19. aethergraph/config/config.py +22 -0
  20. aethergraph/config/loader.py +3 -2
  21. aethergraph/config/storage.py +209 -0
  22. aethergraph/contracts/__init__.py +0 -0
  23. aethergraph/contracts/services/__init__.py +0 -0
  24. aethergraph/contracts/services/artifacts.py +27 -14
  25. aethergraph/contracts/services/memory.py +45 -17
  26. aethergraph/contracts/services/metering.py +129 -0
  27. aethergraph/contracts/services/runs.py +50 -0
  28. aethergraph/contracts/services/sessions.py +87 -0
  29. aethergraph/contracts/services/state_stores.py +3 -0
  30. aethergraph/contracts/services/viz.py +44 -0
  31. aethergraph/contracts/storage/artifact_index.py +88 -0
  32. aethergraph/contracts/storage/artifact_store.py +99 -0
  33. aethergraph/contracts/storage/async_kv.py +34 -0
  34. aethergraph/contracts/storage/blob_store.py +50 -0
  35. aethergraph/contracts/storage/doc_store.py +35 -0
  36. aethergraph/contracts/storage/event_log.py +31 -0
  37. aethergraph/contracts/storage/vector_index.py +48 -0
  38. aethergraph/core/__init__.py +0 -0
  39. aethergraph/core/execution/forward_scheduler.py +13 -2
  40. aethergraph/core/execution/global_scheduler.py +21 -15
  41. aethergraph/core/execution/step_forward.py +10 -1
  42. aethergraph/core/graph/__init__.py +0 -0
  43. aethergraph/core/graph/graph_builder.py +8 -4
  44. aethergraph/core/graph/graph_fn.py +156 -15
  45. aethergraph/core/graph/graph_spec.py +8 -0
  46. aethergraph/core/graph/graphify.py +146 -27
  47. aethergraph/core/graph/node_spec.py +0 -2
  48. aethergraph/core/graph/node_state.py +3 -0
  49. aethergraph/core/graph/task_graph.py +39 -1
  50. aethergraph/core/runtime/__init__.py +0 -0
  51. aethergraph/core/runtime/ad_hoc_context.py +64 -4
  52. aethergraph/core/runtime/base_service.py +28 -4
  53. aethergraph/core/runtime/execution_context.py +13 -15
  54. aethergraph/core/runtime/graph_runner.py +222 -37
  55. aethergraph/core/runtime/node_context.py +510 -6
  56. aethergraph/core/runtime/node_services.py +12 -5
  57. aethergraph/core/runtime/recovery.py +15 -1
  58. aethergraph/core/runtime/run_manager.py +783 -0
  59. aethergraph/core/runtime/run_manager_local.py +204 -0
  60. aethergraph/core/runtime/run_registration.py +2 -2
  61. aethergraph/core/runtime/run_types.py +89 -0
  62. aethergraph/core/runtime/runtime_env.py +136 -7
  63. aethergraph/core/runtime/runtime_metering.py +71 -0
  64. aethergraph/core/runtime/runtime_registry.py +36 -13
  65. aethergraph/core/runtime/runtime_services.py +194 -6
  66. aethergraph/core/tools/builtins/toolset.py +1 -1
  67. aethergraph/core/tools/toolkit.py +5 -0
  68. aethergraph/plugins/agents/default_chat_agent copy.py +90 -0
  69. aethergraph/plugins/agents/default_chat_agent.py +171 -0
  70. aethergraph/plugins/agents/shared.py +81 -0
  71. aethergraph/plugins/channel/adapters/webui.py +112 -112
  72. aethergraph/plugins/channel/routes/webui_routes.py +367 -102
  73. aethergraph/plugins/channel/utils/slack_utils.py +115 -59
  74. aethergraph/plugins/channel/utils/telegram_utils.py +88 -47
  75. aethergraph/plugins/channel/websockets/weibui_ws.py +172 -0
  76. aethergraph/runtime/__init__.py +15 -0
  77. aethergraph/server/app_factory.py +196 -34
  78. aethergraph/server/clients/channel_client.py +202 -0
  79. aethergraph/server/http/channel_http_routes.py +116 -0
  80. aethergraph/server/http/channel_ws_routers.py +45 -0
  81. aethergraph/server/loading.py +117 -0
  82. aethergraph/server/server.py +131 -0
  83. aethergraph/server/server_state.py +240 -0
  84. aethergraph/server/start.py +227 -66
  85. aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  86. aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  87. aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  88. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  89. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  90. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  91. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  92. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  93. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  94. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  95. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  96. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  97. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  98. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  99. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  100. aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  101. aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  102. aethergraph/server/ui_static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  103. aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  104. aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  105. aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  106. aethergraph/server/ui_static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  107. aethergraph/server/ui_static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  108. aethergraph/server/ui_static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  109. aethergraph/server/ui_static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  110. aethergraph/server/ui_static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  111. aethergraph/server/ui_static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  112. aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  113. aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  114. aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  115. aethergraph/server/ui_static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  116. aethergraph/server/ui_static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  117. aethergraph/server/ui_static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  118. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  119. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  120. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  121. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  122. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  123. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  124. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  125. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  126. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  127. aethergraph/server/ui_static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  128. aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  129. aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  130. aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  131. aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  132. aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  133. aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  134. aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  135. aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  136. aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  137. aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  138. aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  139. aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  140. aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  141. aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  142. aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  143. aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  144. aethergraph/server/ui_static/assets/index-BR5GtXcZ.css +1 -0
  145. aethergraph/server/ui_static/assets/index-CQ0HZZ83.js +400 -0
  146. aethergraph/server/ui_static/index.html +15 -0
  147. aethergraph/server/ui_static/logo.png +0 -0
  148. aethergraph/services/artifacts/__init__.py +0 -0
  149. aethergraph/services/artifacts/facade.py +1239 -132
  150. aethergraph/services/auth/{dev.py → authn.py} +0 -8
  151. aethergraph/services/auth/authz.py +100 -0
  152. aethergraph/services/channel/__init__.py +0 -0
  153. aethergraph/services/channel/channel_bus.py +19 -1
  154. aethergraph/services/channel/factory.py +13 -1
  155. aethergraph/services/channel/ingress.py +311 -0
  156. aethergraph/services/channel/queue_adapter.py +75 -0
  157. aethergraph/services/channel/session.py +502 -19
  158. aethergraph/services/container/default_container.py +122 -43
  159. aethergraph/services/continuations/continuation.py +6 -0
  160. aethergraph/services/continuations/stores/fs_store.py +19 -0
  161. aethergraph/services/eventhub/event_hub.py +76 -0
  162. aethergraph/services/kv/__init__.py +0 -0
  163. aethergraph/services/kv/ephemeral.py +244 -0
  164. aethergraph/services/llm/__init__.py +0 -0
  165. aethergraph/services/llm/generic_client copy.py +691 -0
  166. aethergraph/services/llm/generic_client.py +1288 -187
  167. aethergraph/services/llm/providers.py +3 -1
  168. aethergraph/services/llm/types.py +47 -0
  169. aethergraph/services/llm/utils.py +284 -0
  170. aethergraph/services/logger/std.py +3 -0
  171. aethergraph/services/mcp/__init__.py +9 -0
  172. aethergraph/services/mcp/http_client.py +38 -0
  173. aethergraph/services/mcp/service.py +225 -1
  174. aethergraph/services/mcp/stdio_client.py +41 -6
  175. aethergraph/services/mcp/ws_client.py +44 -2
  176. aethergraph/services/memory/__init__.py +0 -0
  177. aethergraph/services/memory/distillers/llm_long_term.py +234 -0
  178. aethergraph/services/memory/distillers/llm_meta_summary.py +398 -0
  179. aethergraph/services/memory/distillers/long_term.py +225 -0
  180. aethergraph/services/memory/facade/__init__.py +3 -0
  181. aethergraph/services/memory/facade/chat.py +440 -0
  182. aethergraph/services/memory/facade/core.py +447 -0
  183. aethergraph/services/memory/facade/distillation.py +424 -0
  184. aethergraph/services/memory/facade/rag.py +410 -0
  185. aethergraph/services/memory/facade/results.py +315 -0
  186. aethergraph/services/memory/facade/retrieval.py +139 -0
  187. aethergraph/services/memory/facade/types.py +77 -0
  188. aethergraph/services/memory/facade/utils.py +43 -0
  189. aethergraph/services/memory/facade_dep.py +1539 -0
  190. aethergraph/services/memory/factory.py +9 -3
  191. aethergraph/services/memory/utils.py +10 -0
  192. aethergraph/services/metering/eventlog_metering.py +470 -0
  193. aethergraph/services/metering/noop.py +25 -4
  194. aethergraph/services/rag/__init__.py +0 -0
  195. aethergraph/services/rag/facade.py +279 -23
  196. aethergraph/services/rag/index_factory.py +2 -2
  197. aethergraph/services/rag/node_rag.py +317 -0
  198. aethergraph/services/rate_limit/inmem_rate_limit.py +24 -0
  199. aethergraph/services/registry/__init__.py +0 -0
  200. aethergraph/services/registry/agent_app_meta.py +419 -0
  201. aethergraph/services/registry/registry_key.py +1 -1
  202. aethergraph/services/registry/unified_registry.py +74 -6
  203. aethergraph/services/scope/scope.py +159 -0
  204. aethergraph/services/scope/scope_factory.py +164 -0
  205. aethergraph/services/state_stores/serialize.py +5 -0
  206. aethergraph/services/state_stores/utils.py +2 -1
  207. aethergraph/services/viz/__init__.py +0 -0
  208. aethergraph/services/viz/facade.py +413 -0
  209. aethergraph/services/viz/viz_service.py +69 -0
  210. aethergraph/storage/artifacts/artifact_index_jsonl.py +180 -0
  211. aethergraph/storage/artifacts/artifact_index_sqlite.py +426 -0
  212. aethergraph/storage/artifacts/cas_store.py +422 -0
  213. aethergraph/storage/artifacts/fs_cas.py +18 -0
  214. aethergraph/storage/artifacts/s3_cas.py +14 -0
  215. aethergraph/storage/artifacts/utils.py +124 -0
  216. aethergraph/storage/blob/fs_blob.py +86 -0
  217. aethergraph/storage/blob/s3_blob.py +115 -0
  218. aethergraph/storage/continuation_store/fs_cont.py +283 -0
  219. aethergraph/storage/continuation_store/inmem_cont.py +146 -0
  220. aethergraph/storage/continuation_store/kvdoc_cont.py +261 -0
  221. aethergraph/storage/docstore/fs_doc.py +63 -0
  222. aethergraph/storage/docstore/sqlite_doc.py +31 -0
  223. aethergraph/storage/docstore/sqlite_doc_sync.py +90 -0
  224. aethergraph/storage/eventlog/fs_event.py +136 -0
  225. aethergraph/storage/eventlog/sqlite_event.py +47 -0
  226. aethergraph/storage/eventlog/sqlite_event_sync.py +178 -0
  227. aethergraph/storage/factory.py +432 -0
  228. aethergraph/storage/fs_utils.py +28 -0
  229. aethergraph/storage/graph_state_store/state_store.py +64 -0
  230. aethergraph/storage/kv/inmem_kv.py +103 -0
  231. aethergraph/storage/kv/layered_kv.py +52 -0
  232. aethergraph/storage/kv/sqlite_kv.py +39 -0
  233. aethergraph/storage/kv/sqlite_kv_sync.py +98 -0
  234. aethergraph/storage/memory/event_persist.py +68 -0
  235. aethergraph/storage/memory/fs_persist.py +118 -0
  236. aethergraph/{services/memory/hotlog_kv.py → storage/memory/hotlog.py} +8 -2
  237. aethergraph/{services → storage}/memory/indices.py +31 -7
  238. aethergraph/storage/metering/meter_event.py +55 -0
  239. aethergraph/storage/runs/doc_store.py +280 -0
  240. aethergraph/storage/runs/inmen_store.py +82 -0
  241. aethergraph/storage/runs/sqlite_run_store.py +403 -0
  242. aethergraph/storage/sessions/doc_store.py +183 -0
  243. aethergraph/storage/sessions/inmem_store.py +110 -0
  244. aethergraph/storage/sessions/sqlite_session_store.py +399 -0
  245. aethergraph/storage/vector_index/chroma_index.py +138 -0
  246. aethergraph/storage/vector_index/faiss_index.py +179 -0
  247. aethergraph/storage/vector_index/sqlite_index.py +187 -0
  248. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/METADATA +138 -31
  249. aethergraph-0.1.0a3.dist-info/RECORD +356 -0
  250. aethergraph-0.1.0a3.dist-info/entry_points.txt +3 -0
  251. aethergraph/services/artifacts/factory.py +0 -35
  252. aethergraph/services/artifacts/fs_store.py +0 -656
  253. aethergraph/services/artifacts/jsonl_index.py +0 -123
  254. aethergraph/services/artifacts/sqlite_index.py +0 -209
  255. aethergraph/services/memory/distillers/episode.py +0 -116
  256. aethergraph/services/memory/distillers/rolling.py +0 -74
  257. aethergraph/services/memory/facade.py +0 -633
  258. aethergraph/services/memory/persist_fs.py +0 -40
  259. aethergraph/services/rag/index/base.py +0 -27
  260. aethergraph/services/rag/index/faiss_index.py +0 -121
  261. aethergraph/services/rag/index/sqlite_index.py +0 -134
  262. aethergraph-0.1.0a1.dist-info/RECORD +0 -182
  263. aethergraph-0.1.0a1.dist-info/entry_points.txt +0 -2
  264. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/WHEEL +0 -0
  265. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/licenses/LICENSE +0 -0
  266. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/licenses/NOTICE +0 -0
  267. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/top_level.txt +0 -0
@@ -1,119 +1,280 @@
1
- # aethergraph/start.py
2
1
  from __future__ import annotations
3
2
 
4
- import asyncio
5
3
  import contextlib
6
- import socket
4
+ from dataclasses import dataclass
5
+ import os
6
+ from pathlib import Path
7
7
  import threading
8
+ import time
9
+ from typing import Any
8
10
 
11
+ from fastapi import FastAPI
9
12
  import uvicorn
10
13
 
11
14
  from aethergraph.config.context import set_current_settings
12
15
  from aethergraph.config.loader import load_settings
16
+ from aethergraph.server.loading import GraphLoader, LoadSpec
17
+ from aethergraph.server.server_state import (
18
+ get_running_url_if_any,
19
+ pick_free_port,
20
+ workspace_lock,
21
+ write_server_state,
22
+ )
13
23
 
14
- from ..plugins.channel.routes.webui_routes import install_web_channel
15
24
  from .app_factory import create_app
16
25
 
17
26
  _started = False
18
27
  _server_thread: threading.Thread | None = None
19
- _shutdown_flag = threading.Event()
20
28
  _url: str | None = None
29
+ _uvicorn_server: uvicorn.Server | None = None
30
+ _loader = GraphLoader()
21
31
 
22
32
 
23
- def _pick_free_port(p: int) -> int:
24
- if p:
25
- return p
26
- s = socket.socket()
27
- s.bind(("", 0))
28
- port = s.getsockname()[1]
29
- s.close()
30
- return port
33
+ @dataclass
34
+ class ServerHandle:
35
+ url: str
36
+ server: uvicorn.Server
37
+ thread: threading.Thread
31
38
 
39
+ def stop(self, timeout_s: float = 2.0) -> None:
40
+ self.server.should_exit = True
41
+ self.thread.join(timeout=timeout_s)
32
42
 
33
- def _run_uvicorn_in_thread(app, host: str, port: int, log_level: str):
34
- loop = asyncio.new_event_loop()
35
- asyncio.set_event_loop(loop)
36
- server = uvicorn.Server(
37
- uvicorn.Config(app, host=host, port=port, log_level=log_level, loop="asyncio")
38
- )
43
+ def block(self) -> None:
44
+ # Loop with a timeout allows Python to process signals (like Ctrl+C)
45
+ while self.thread.is_alive():
46
+ self.thread.join(timeout=1.0)
39
47
 
40
- async def runner():
41
- task = asyncio.create_task(server.serve())
42
- while not _shutdown_flag.is_set():
43
- await asyncio.sleep(0.2)
44
- if not server.should_exit:
45
- server.should_exit = True
46
- await task
47
48
 
48
- try:
49
- loop.run_until_complete(runner())
50
- finally:
51
- loop.stop()
52
- loop.close()
49
+ def _make_uvicorn_server(app: FastAPI, host: str, port: int, log_level: str) -> uvicorn.Server:
50
+ """
51
+ Create a uvicorn.Server we can stop via server.should_exit = True.
52
+ (Safe for background thread.)
53
+ """
54
+ config = uvicorn.Config(
55
+ app=app,
56
+ host=host,
57
+ port=port,
58
+ log_level=log_level,
59
+ lifespan="on",
60
+ loop="asyncio",
61
+ )
62
+ server = uvicorn.Server(config=config)
63
+ server.install_signal_handlers = lambda: None # type: ignore
64
+ return server
53
65
 
54
66
 
55
67
  def start_server(
56
68
  *,
57
69
  workspace: str = "./aethergraph_data",
58
70
  host: str = "127.0.0.1",
59
- port: int = 8000, # 0 = auto free port
71
+ port: int = 8745, # 0 = auto free port
60
72
  log_level: str = "warning",
61
73
  unvicorn_log_level: str = "warning",
62
74
  return_container: bool = False,
63
- ) -> str:
75
+ return_handle: bool = False,
76
+ load_modules: list[str] | None = None,
77
+ load_paths: list[str] | None = None,
78
+ project_root: str | None = None,
79
+ strict_load: bool = False,
80
+ ) -> str | tuple[str, Any] | tuple[str, ServerHandle] | tuple[str, Any, ServerHandle]:
64
81
  """
65
- Start the AetherGraph sidecar server in a background thread and install
66
- services using the given workspace. Safe to call at top of any script
67
- or notebook cell (no main() wrapper needed). Returns base URL.
82
+ Start (or reuse) the AetherGraph sidecar server in a normalized and flexible way.
83
+
84
+ This method manages server lifecycle, workspace locking, and dynamic loading of user code.
85
+ It supports both in-process and cross-process server reuse, and can return handles for
86
+ advanced control or integration.
87
+
88
+ Examples:
89
+ Basic usage to start a server and get its URL:
90
+ ```python
91
+ url = start_server(workspace="./aethergraph_data", port=0)
92
+ ```
93
+
94
+ Loading user graphs before starting:
95
+ ```python
96
+ url = start_server(
97
+ workspace="./aethergraph_data",
98
+ port=0,
99
+ load_paths=["./my_graphs.py"],
100
+ project_root=".",
101
+ )
102
+ ```
103
+
104
+ Starting and blocking until server exit (notebook/script mode):
105
+ ```python
106
+ url, handle = start_server(workspace="./aethergraph_data", port=0, return_handle=True)
107
+ print("Server running at", url)
108
+ try:
109
+ handle.block()
110
+ except KeyboardInterrupt:
111
+ print("Stopping server...")
112
+ handle.stop()
113
+ ```
114
+
115
+ Returning the dependency injection container for advanced use:
116
+ ```python
117
+ url, container = start_server(workspace="./aethergraph_data", return_container=True)
118
+ ```
119
+
120
+ Returning both container and handle:
121
+ ```python
122
+ url, container, handle = start_server(
123
+ workspace="./aethergraph_data",
124
+ return_container=True,
125
+ return_handle=True,
126
+ )
127
+ ```
128
+
129
+ Args:
130
+ workspace: Persistent storage directory for server state and data.
131
+ host: Host address to bind the server (default "127.0.0.1").
132
+ port: Port to bind the server (0 for auto-pick, or specify a fixed port).
133
+ log_level: Logging level for the application.
134
+ unvicorn_log_level: Logging level for the Uvicorn server.
135
+ return_container: If True, also return the app's dependency injection container.
136
+ return_handle: If True, return a ServerHandle for programmatic control (block/stop).
137
+ load_modules: List of Python modules to import before server start.
138
+ load_paths: List of Python file paths to import before server start.
139
+ project_root: Path to add to sys.path for module resolution during loading.
140
+ strict_load: If True, raise on import/load errors; otherwise, record errors in loader report.
141
+
142
+ Returns:
143
+ str: The server URL (e.g., "http://127.0.0.1:53421").
144
+ tuple: Optionally, (url, container), (url, handle), or (url, container, handle)
145
+ depending on the flags set and whether the server was started in-process.
146
+
147
+ Notes:
148
+ - Workspace is a dedicated directory for server data, including logs, caches, and runtime state; multiple processes using the same
149
+ workspace will coordinate to reuse a single server instance. Delete the workspace to reset state.
150
+ - Use handle.block() to wait for server exit when you need to keep the server running in a script or notebook. This is typically not needed
151
+ when using the server in client mode.
152
+ - When you are using Aethergraph UI, use handle.block() to keep the server running so that the UI can connect to it and discover agents/apps.
68
153
  """
69
- global _started, _server_thread, _url
70
- if _started:
71
- return _url # type: ignore
72
-
73
- # Build app (installs services inside create_app)
74
- cfg = load_settings()
75
- set_current_settings(cfg)
76
-
77
- app = create_app(workspace=workspace, cfg=cfg, log_level=log_level)
78
-
79
- picked_port = _pick_free_port(port)
80
- t = threading.Thread(
81
- target=_run_uvicorn_in_thread,
82
- args=(app, host, picked_port, unvicorn_log_level),
83
- name="aethergraph-sidecar",
84
- daemon=True,
85
- )
86
- t.start()
154
+ global _started, _server_thread, _url, _uvicorn_server
155
+
156
+ # In-process fast path
157
+ if _started and _url:
158
+ if return_container or return_handle:
159
+ # We can return these because we're in-process
160
+ # (container is attached to app only when we start it; see below)
161
+ pass
162
+ else:
163
+ print(" - reusing existing in-process server at", _url)
164
+ return _url
87
165
 
88
- _server_thread = t
89
- _started = True
90
- _url = f"http://{host}:{picked_port}"
166
+ print(" - acquiring workspace lock...")
167
+ # Cross-process coordination: one workspace => one server
168
+ with workspace_lock(workspace):
169
+ running_url = get_running_url_if_any(workspace)
170
+ if running_url:
171
+ # Reuse the already-running sidecar for this workspace
172
+ print(" - reusing existing sidecar server at", running_url)
173
+ _started = True
174
+ _url = running_url
175
+ # Cross-process: we cannot return container/handle
176
+ return running_url
91
177
 
92
- install_web_channel(app)
178
+ # Load graphs BEFORE server start so /apps, /agents are populated immediately
179
+ spec = LoadSpec(
180
+ modules=load_modules or [],
181
+ paths=load_paths or [],
182
+ project_root=project_root,
183
+ strict=strict_load,
184
+ )
93
185
 
94
- if return_container:
95
- return _url, app.state.container
96
- return _url
186
+ print(" Loading user graphs with spec:", spec)
187
+ if spec.modules or spec.paths:
188
+ report = _loader.load(spec)
189
+ # Optional: stash report for debugging. We'll attach it to app below.
190
+ _loader.last_report = report
191
+
192
+ # Build app (installs services inside create_app)
193
+ cfg = load_settings()
194
+ set_current_settings(cfg)
195
+ app = create_app(workspace=workspace, cfg=cfg, log_level=log_level)
196
+ # Optional debug info
197
+ app.state.last_load_report = getattr(_loader, "last_report", None)
198
+
199
+ picked_port = pick_free_port(port)
200
+ url = f"http://{host}:{picked_port}"
201
+
202
+ # Create stoppable server object
203
+ server = _make_uvicorn_server(app, host, picked_port, unvicorn_log_level)
204
+
205
+ def _target():
206
+ server.run()
207
+
208
+ t = threading.Thread(
209
+ target=_target,
210
+ name="aethergraph-sidecar",
211
+ daemon=True,
212
+ )
213
+ t.start()
214
+
215
+ # Update globals
216
+ _server_thread = t
217
+ _uvicorn_server = server
218
+ _started = True
219
+ _url = url
220
+
221
+ # Write server.json for discovery
222
+ write_server_state(
223
+ workspace,
224
+ {
225
+ "pid": os.getpid(),
226
+ "host": host,
227
+ "port": picked_port,
228
+ "url": url,
229
+ "workspace": str(Path(workspace).resolve()),
230
+ "started_at": time.time(),
231
+ },
232
+ )
233
+
234
+ print("\n" + "=" * 50)
235
+ # We align the labels to 18 characters (the length of the longest label)
236
+ print(f"[AetherGraph] 🚀 {'Server started at:':<18} {url}")
237
+ print(
238
+ f"[AetherGraph] 🖥️ {'UI:':<18} {url}/ui (if built)"
239
+ ) # strangly, this needs two spaces unlike the rest
240
+ print(f"[AetherGraph] 📡 {'API:':<18} {url}/api/v1/")
241
+ print(f"[AetherGraph] 📂 {'Workspace:':<18} {workspace}")
242
+ print("=" * 50 + "\n")
243
+
244
+ handle = ServerHandle(url=url, server=server, thread=t)
245
+
246
+ if return_container and return_handle:
247
+ return url, app.state.container, handle
248
+ if return_container:
249
+ return url, app.state.container
250
+ if return_handle:
251
+ return url, handle
252
+
253
+ return url
97
254
 
98
255
 
99
256
  async def start_server_async(**kw) -> str:
100
257
  # Async-friendly wrapper; still uses a thread to avoid clashing with caller loop
101
- return start_server(**kw)
258
+ return start_server(**kw) # type: ignore[return-value]
102
259
 
103
260
 
104
261
  def stop_server():
105
- """Optional: stop the background server (useful in tests)."""
106
- global _started, _server_thread, _url
262
+ """Stop the in-process background server (useful in tests/notebooks)."""
263
+ global _started, _server_thread, _url, _uvicorn_server
107
264
  if not _started:
108
265
  return
109
- _shutdown_flag.set()
266
+
267
+ if _uvicorn_server is not None:
268
+ _uvicorn_server.should_exit = True
269
+
110
270
  if _server_thread and _server_thread.is_alive():
111
271
  with contextlib.suppress(Exception):
112
272
  _server_thread.join(timeout=5)
273
+
113
274
  _started = False
114
275
  _server_thread = None
276
+ _uvicorn_server = None
115
277
  _url = None
116
- _shutdown_flag.clear()
117
278
 
118
279
 
119
280
  # backward compatibility