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
@@ -5,26 +5,45 @@ from pathlib import Path
5
5
  from typing import Any
6
6
 
7
7
  from aethergraph.contracts.services.llm import LLMClientProtocol
8
+ from aethergraph.core.runtime.base_service import Service
8
9
  from aethergraph.services.llm.generic_client import GenericLLMClient
9
10
 
10
11
  _current = ContextVar("aeg_services", default=None)
11
12
  # process-wide fallback (handles contextvar boundary issues)
12
13
  _services_global: Any = None
14
+ # allow registering external services before main services are ready
15
+ _pending_ext_services: dict[str, Any] = {}
13
16
 
14
17
 
15
18
  def install_services(services: Any) -> None:
16
- global _services_global
19
+ global _services_global, _pending_ext_services
17
20
  _services_global = services
21
+
22
+ # Attach any services that were registered before install_services().
23
+ ext = getattr(services, "ext_services", None)
24
+ if isinstance(ext, dict) and _pending_ext_services:
25
+ # Don't clobber anything that was already present.
26
+ for name, svc in _pending_ext_services.items():
27
+ ext.setdefault(name, svc)
28
+ _pending_ext_services = {}
29
+
18
30
  return _current.set(services)
19
31
 
20
32
 
21
33
  def ensure_services_installed(factory: Callable[[], Any]) -> Any:
22
- global _services_global
34
+ global _services_global, _pending_ext_services
23
35
  svc = _current.get() or _services_global
24
36
  if svc is None:
25
37
  svc = factory()
26
38
  _services_global = svc
27
- _current.set(svc) # keep ContextVar in sync for this context
39
+
40
+ # hydrate pending external services here too
41
+ ext = getattr(svc, "ext_services", None)
42
+ if isinstance(ext, dict) and _pending_ext_services:
43
+ for name, s in _pending_ext_services.items():
44
+ ext.setdefault(name, s)
45
+ _pending_ext_services = {}
46
+ _current.set(svc)
28
47
  return svc
29
48
 
30
49
 
@@ -174,33 +193,182 @@ def current_logger_factory() -> Any:
174
193
 
175
194
 
176
195
  # --------- External context services ---------
177
- def register_context_service(name: str, service: Any) -> None:
178
- svc = current_services()
196
+ def register_context_service(name: str, service: Service) -> None:
197
+ """
198
+ Register an external service for NodeContext access.
199
+
200
+ This function attaches an external service to the current service container
201
+ under the specified name. If no container is installed yet, the service is
202
+ stashed in a pending registry and will be attached automatically when
203
+ install_services() is called.
204
+
205
+ Examples:
206
+ Register a custom database service:
207
+ ```python
208
+ register_context_service("mydb", MyDatabaseService())
209
+ ```
210
+
211
+ Args:
212
+ name: The unique string identifier for the external service.
213
+ service: The service instance to register.
214
+
215
+ Returns:
216
+ None
217
+
218
+ Notes:
219
+ - If called before install_services(), the service will be attached later.
220
+ - Services are accessible via NodeContext.ext_services[name].
221
+ """
222
+ global _pending_ext_services
223
+
224
+ try:
225
+ svc = current_services()
226
+ except RuntimeError:
227
+ # No container yet: keep it in the staging area.
228
+ _pending_ext_services[name] = service
229
+ return
230
+
231
+ # Container exists: attach immediately.
179
232
  svc.ext_services[name] = service
180
233
 
181
234
 
182
- def get_ext_context_service(name: str) -> Any:
235
+ def get_ext_context_service(name: str) -> Service:
236
+ """
237
+ Retrieve an external context service by name.
238
+
239
+ This function returns the external service registered under the given name
240
+ from the current service container's ext_services registry.
241
+
242
+ Examples:
243
+ Access a registered service:
244
+ ```python
245
+ mydb = get_ext_context_service("mydb")
246
+ ```
247
+
248
+ Args:
249
+ name: The string name of the external service to retrieve.
250
+
251
+ Returns:
252
+ The service instance registered under the given name, or None if not found.
253
+
254
+ Raises:
255
+ RuntimeError: If no services container is installed.
256
+ """
183
257
  svc = current_services()
184
258
  return svc.ext_services.get(name)
185
259
 
186
260
 
187
261
  def list_ext_context_services() -> list[str]:
262
+ """
263
+ List all registered external context service names.
264
+
265
+ This function returns a list of all names for services currently registered
266
+ in the ext_services registry of the current service container.
267
+
268
+ Examples:
269
+ List all available external services:
270
+ ```python
271
+ services = list_ext_context_services()
272
+ print(services)
273
+ ```
274
+
275
+ Args:
276
+ None
277
+
278
+ Returns:
279
+ A list of strings representing the names of all registered external services.
280
+ Returns an empty list if no services are registered.
281
+
282
+ Raises:
283
+ RuntimeError: If no services container is installed.
284
+ """
188
285
  svc = current_services()
189
286
  return list(svc.ext_services.keys())
190
287
 
191
288
 
192
289
  # --------- MCP service helpers ---------
193
290
  def set_mcp_service(mcp_service: Any) -> None:
291
+ """
292
+ Set the MCP service in the current service container.
293
+
294
+ This function assigns the provided MCP service instance to the current application's
295
+ service container, making it available for subsequent MCP client registrations and lookups.
296
+
297
+ Examples:
298
+ ```python
299
+ from aethergraph.runtime import set_mcp_service
300
+ set_mcp_service(MyMCPService())
301
+ ```
302
+
303
+ Args:
304
+ mcp_service: An instance implementing the MCP service interface.
305
+
306
+ Returns:
307
+ None
308
+
309
+ Notes:
310
+ - This should be called once during application startup before registering MCP clients.
311
+ - This is an internal function; users typically interact with MCP services via higher-level APIs.
312
+ """
194
313
  svc = current_services()
195
314
  svc.mcp = mcp_service
196
315
 
197
316
 
198
317
  def get_mcp_service() -> Any:
318
+ """
319
+ Retrieve the currently configured MCP service.
320
+
321
+ This function returns the MCP service instance from the current application's
322
+ service container. It is used to access MCP-related functionality throughout the app.
323
+
324
+ Examples:
325
+ ```python
326
+ mcp = get_mcp_service()
327
+ ```
328
+
329
+ Args:
330
+ None
331
+
332
+ Returns:
333
+ The MCP service instance currently set in the service container.
334
+
335
+ Raises:
336
+ RuntimeError: If no MCP service has been set.
337
+
338
+ Notes:
339
+ - Ensure that set_mcp_service() has been called during application initialization.
340
+ - This is an internal function; users typically interact with MCP services via higher-level APIs.
341
+ """
199
342
  svc = current_services()
200
343
  return svc.mcp
201
344
 
202
345
 
203
346
  def register_mcp_client(name: str, client: Any) -> None:
347
+ """
348
+ Register a new MCP client with the current MCP service.
349
+
350
+ This function adds a client instance to the MCP service under the specified name,
351
+ allowing it to be accessed and managed by the MCP infrastructure.
352
+
353
+ Examples:
354
+ ```python
355
+ from aethergraph.runtime import register_mcp_client
356
+ from aethergraph.services.mcp import HttpMCPClient
357
+ my_client = HttpMCPClient("https://mcp.example.com", ...)
358
+ register_mcp_client("myclient", my_client)
359
+ ```
360
+
361
+ Args:
362
+ name: The unique name to associate with the MCP client.
363
+ client: The client instance to register.
364
+
365
+ Returns:
366
+ None
367
+
368
+ Raises:
369
+ RuntimeError: If no MCP service has been installed via set_mcp_service().
370
+
371
+ """
204
372
  svc = current_services()
205
373
  if svc.mcp is None:
206
374
  raise RuntimeError("No MCP service installed. Call set_mcp_service() first.")
@@ -208,6 +376,26 @@ def register_mcp_client(name: str, client: Any) -> None:
208
376
 
209
377
 
210
378
  def list_mcp_clients() -> list[str]:
379
+ """
380
+ List all registered MCP client names in the current MCP service.
381
+
382
+ This function returns a list of all client names that have been registered
383
+ with the MCP service, allowing for discovery and management of available clients.
384
+
385
+ Examples:
386
+ ```python
387
+ from aethergraph.runtime import list_mcp_clients
388
+ clients = list_mcp_clients()
389
+ print(clients)
390
+ ```
391
+
392
+ Args:
393
+ None
394
+
395
+ Returns:
396
+ A list of strings representing the names of registered MCP clients.
397
+ Returns an empty list if no MCP service is installed or no clients are registered.
398
+ """
211
399
  svc = current_services()
212
400
  if svc.mcp:
213
401
  return svc.mcp.list_clients()
@@ -77,7 +77,7 @@ async def ask_files(
77
77
 
78
78
  @tool(name="send_text", outputs=["ok"])
79
79
  async def send_text(
80
- *, text: str, meta: dict[str, Any] | None = None, channel: str | None = None, context=None
80
+ text: str, *, meta: dict[str, Any] | None = None, channel: str | None = None, context=None
81
81
  ):
82
82
  ch = context.channel(channel)
83
83
  await ch.send_text(text, meta=meta or {})
@@ -141,11 +141,16 @@ def tool(
141
141
  # registry behavior
142
142
  registry = current_registry()
143
143
  if registry is not None:
144
+ meta = {
145
+ "kind": "tool",
146
+ "tags": [],
147
+ }
144
148
  registry.register(
145
149
  nspace="tool",
146
150
  name=name or getattr(impl, "__name__", "tool"),
147
151
  version=version,
148
152
  obj=impl,
153
+ meta=meta,
149
154
  )
150
155
 
151
156
  return proxy
@@ -0,0 +1,90 @@
1
+ # aethergraph/examples/agents/default_chat_agent.py (or similar)
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from aethergraph import NodeContext, graph_fn
8
+ from aethergraph.plugins.agents.shared import build_session_memory_prompt_segments
9
+
10
+
11
+ @graph_fn(
12
+ name="default_chat_agent",
13
+ inputs=["message", "files", "session_id", "user_meta"],
14
+ outputs=["reply"],
15
+ as_agent={
16
+ "id": "chat_agent",
17
+ "title": "Chat",
18
+ "description": "Built-in chat agent that uses the configured LLM.",
19
+ "icon": "message-circle",
20
+ "color": "sky",
21
+ "session_kind": "chat",
22
+ "mode": "chat_v1",
23
+ "memory_level": "session",
24
+ "memory_scope": "session.global",
25
+ },
26
+ )
27
+ async def default_chat_agent(
28
+ message: str,
29
+ files: list[Any] | None = None,
30
+ session_id: str | None = None,
31
+ user_meta: dict[str, Any] | None = None,
32
+ context_refs: list[dict[str, Any]] | None = None,
33
+ *,
34
+ context: NodeContext,
35
+ ):
36
+ """
37
+ Simple built-in chat agent:
38
+
39
+ - Takes {message, files}
40
+ - Calls the configured LLM
41
+ - Uses shared session memory (summary + recent events) in the prompt.
42
+ """
43
+
44
+ llm = context.llm()
45
+ chan = context.ui_session_channel()
46
+
47
+ # 1) Build memory segments for this session
48
+ session_summary, recent_events = await build_session_memory_prompt_segments(
49
+ context,
50
+ summary_tag="session",
51
+ recent_limit=12,
52
+ )
53
+
54
+ # print("Session summary:", session_summary)
55
+ # print("Recent events:", recent_events)
56
+
57
+ # 2) System + user messages (you can move this into PromptStore later)
58
+ system_prompt = (
59
+ "You are AetherGraph's built-in session helper.\n\n"
60
+ "You can see a short summary of the session and a few recent events from all agents.\n"
61
+ "Use them to answer questions about previous steps or runs, but do not invent details.\n"
62
+ "If you are unsure, say that clearly.\n"
63
+ "When return math or code snippets, use markdown formatting.\n"
64
+ "Math formatting rules:\n"
65
+ "- Use LaTeX math delimiters:\n"
66
+ " - Inline: \\( ... \\) (no extra spaces right after \\( or before \\))\n"
67
+ " - Display: $$ ... $$ (for standalone equations)\n"
68
+ )
69
+
70
+ memory_context = ""
71
+ if session_summary:
72
+ memory_context += f"Session summary:\n{session_summary}\n\n"
73
+ if recent_events:
74
+ memory_context += f"Recent events:\n{recent_events}\n\n"
75
+
76
+ user_prompt = f"{memory_context}" "User message:\n" f"{message}\n"
77
+
78
+ # 3) Call LLM with chat-style API
79
+ resp, _usage = await llm.chat(
80
+ messages=[
81
+ {"role": "system", "content": system_prompt},
82
+ {"role": "user", "content": user_prompt},
83
+ ],
84
+ )
85
+
86
+ await chan.send_text(resp)
87
+
88
+ return {
89
+ "reply": resp,
90
+ }
@@ -0,0 +1,171 @@
1
+ # aethergraph/examples/agents/default_chat_agent.py (or similar)
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from aethergraph import NodeContext, graph_fn
8
+
9
+
10
+ @graph_fn(
11
+ name="default_chat_agent",
12
+ inputs=["message", "files", "session_id", "user_meta"],
13
+ outputs=["reply"],
14
+ as_agent={
15
+ "id": "chat_agent",
16
+ "title": "Chat",
17
+ "short_description": "General-purpose chat agent.",
18
+ "description": "Built-in chat agent that uses the configured LLM and memory across sessions.",
19
+ "icon": "message-circle",
20
+ "color": "sky",
21
+ "session_kind": "chat",
22
+ "mode": "chat_v1",
23
+ "memory_level": "session",
24
+ "memory_scope": "session.global",
25
+ },
26
+ )
27
+ async def default_chat_agent(
28
+ message: str,
29
+ files: list[Any] | None = None,
30
+ session_id: str | None = None,
31
+ user_meta: dict[str, Any] | None = None,
32
+ context_refs: list[dict[str, Any]] | None = None,
33
+ *,
34
+ context: NodeContext,
35
+ ):
36
+ """
37
+ Built-in chat agent with session memory:
38
+
39
+ - Hydrates long-term + recent chat memory into the prompt.
40
+ - Records user and assistant messages as chat.turn events.
41
+ - Periodically distills chat history into long-term summaries.
42
+ """
43
+
44
+ llm = context.llm()
45
+ chan = context.ui_session_channel()
46
+
47
+ mem = context.memory()
48
+
49
+ # 1) Build memory segments for this session
50
+ long_term_summary: str = ""
51
+ recent_chat: list[dict[str, Any]] = []
52
+
53
+ """
54
+ Build prompt segments:
55
+ {
56
+ "long_term": "<combined summary text or ''>",
57
+ "recent_chat": [ {ts, role, text, tags}, ... ],
58
+ "recent_tools": [ {ts, tool, message, inputs, outputs, tags}, ... ]
59
+ }
60
+ """
61
+ segments = await mem.build_prompt_segments(
62
+ recent_chat_limit=20,
63
+ include_long_term=True,
64
+ summary_tag="session",
65
+ max_summaries=3,
66
+ include_recent_tools=False,
67
+ )
68
+ long_term_summary = segments.get("long_term") or ""
69
+ recent_chat = segments.get("recent_chat") or []
70
+
71
+ # 2) System prompt
72
+ system_prompt = (
73
+ "You are AetherGraph's built-in session helper.\n\n"
74
+ "You can see a summary of the session and some recent messages.\n"
75
+ "Use them to answer questions about previous steps or runs, but do not invent details.\n"
76
+ "If you are unsure, say that clearly.\n"
77
+ # "When returning math or code snippets, use markdown formatting.\n"
78
+ )
79
+
80
+ messages: list[dict[str, str]] = [
81
+ {"role": "system", "content": system_prompt},
82
+ ]
83
+
84
+ # Inject long-term summary as a system message (if present)
85
+ if long_term_summary:
86
+ messages.append(
87
+ {
88
+ "role": "system",
89
+ "content": "Summary of previous context:\n" + long_term_summary,
90
+ }
91
+ )
92
+
93
+ # Inject recent chat as prior turns
94
+ for item in recent_chat:
95
+ role = item.get("role") or "user"
96
+ text = item.get("text") or ""
97
+ # Map non-standard roles (e.g. "tool") to "assistant" for chat APIs
98
+ mapped_role = role if role in {"user", "assistant", "system"} else "assistant"
99
+ if text:
100
+ messages.append({"role": mapped_role, "content": text})
101
+
102
+ # Add some lightweight metadata about files / context refs into the user message
103
+ meta_lines: list[str] = []
104
+ if files:
105
+ meta_lines.append(f"(User attached {len(files)} file(s).)")
106
+ if context_refs:
107
+ meta_lines.append(f"(User attached {len(context_refs)} context reference(s).)")
108
+ meta_block = ""
109
+ if meta_lines:
110
+ meta_block = "\n\n" + "\n".join(meta_lines)
111
+
112
+ user_content = f"{message}{meta_block}"
113
+
114
+ # 3) Record the user message into memory
115
+ user_data: dict[str, Any] = {}
116
+ if files:
117
+ # Store only lightweight file metadata; avoid huge payloads
118
+ user_data["files"] = [
119
+ {k: v for k, v in (f or {}).items() if k in {"name", "url", "mimetype", "size"}}
120
+ for f in files
121
+ ]
122
+ if context_refs:
123
+ user_data["context_refs"] = context_refs
124
+
125
+ await mem.record_chat_user(
126
+ message,
127
+ data=user_data,
128
+ tags=["session.chat"],
129
+ )
130
+
131
+ # Append current user message to LLM prompt
132
+ messages.append({"role": "user", "content": user_content})
133
+ # 4) Call LLM with chat-style API
134
+ resp, _usage = await llm.chat(
135
+ messages=messages,
136
+ )
137
+
138
+ # 5) Record assistant reply into memory and run simple distillation policy
139
+ try:
140
+ await mem.record_chat_assistant(
141
+ resp,
142
+ tags=["session.chat"],
143
+ )
144
+
145
+ # Simple distillation policy:
146
+ # If we have "enough" chat turns in recent history, run a long-term summary.
147
+ recent_for_distill = await mem.recent_chat(limit=120)
148
+ if len(recent_for_distill) >= 80:
149
+ # Non-LLM summarizer by default; flip use_llm=True later.
150
+ await mem.distill_long_term(
151
+ summary_tag="session",
152
+ summary_kind="long_term_summary",
153
+ include_kinds=["chat.turn"],
154
+ include_tags=["chat"],
155
+ max_events=200,
156
+ use_llm=False,
157
+ )
158
+ except Exception:
159
+ # Memory issues should never break the chat agent
160
+ import traceback
161
+
162
+ trace = traceback.format_exc()
163
+ logger = context.logger()
164
+ logger.warning("Chat agent memory record/distill error:\n" + trace)
165
+
166
+ # 6) Send reply to UI channel
167
+ await chan.send_text(resp)
168
+
169
+ return {
170
+ "reply": resp,
171
+ }
@@ -0,0 +1,81 @@
1
+ from __future__ import annotations
2
+
3
+ from aethergraph import NodeContext
4
+
5
+
6
+ async def build_session_memory_prompt_segments(
7
+ context: NodeContext,
8
+ *,
9
+ summary_tag: str = "session",
10
+ recent_limit: int = 12,
11
+ ) -> tuple[str, str]:
12
+ """
13
+ Build reusable 'memory segments' for LLM prompts:
14
+
15
+ - session_summary: long-term summary text (may be empty)
16
+ - recent_events: short list of recent events (chat, status, etc.)
17
+
18
+ Any agent that wants cross-agent memory can call this, then
19
+ feed these into its LLM prompt or structured prompt store.
20
+
21
+ The underlying MemoryFacade is responsible for:
22
+ - Choosing a scope_id (usually tied to the session).
23
+ - Storing summaries in DocStore (mem/{scope_id}/summaries/{tag}/...).
24
+ - Exposing hotlog events via mem.recent().
25
+ """
26
+ mem = None
27
+ try:
28
+ mem = context.memory()
29
+ except TypeError:
30
+ # Depending on how NodeContext is wired, .memory might be property or method
31
+ mem = getattr(context, "memory", None)
32
+
33
+ if mem is None:
34
+ return "", ""
35
+
36
+ # ---- 1) Long-term session summary (if summarization has been run) ----
37
+ session_summary = ""
38
+ try:
39
+ summary = await mem.soft_hydrate_last_summary(
40
+ summary_tag=summary_tag,
41
+ summary_kind="long_term_summary",
42
+ )
43
+ if summary:
44
+ session_summary = summary.get("text") or ""
45
+ except Exception as e:
46
+ logger = getattr(context, "logger", None)
47
+ if logger:
48
+ logger.warning(
49
+ "build_session_memory_prompt_segments: soft_hydrate_last_summary failed",
50
+ extra={"error": str(e)},
51
+ )
52
+
53
+ # ---- 2) Recent events across runs/agents in this session ----
54
+ recent_events = ""
55
+ try:
56
+ events = await mem.recent(kinds=None, limit=recent_limit)
57
+ lines: list[str] = []
58
+ for evt in events:
59
+ kind = getattr(evt, "kind", None) or "event"
60
+ text = getattr(evt, "text", None)
61
+ data = getattr(evt, "data", None)
62
+
63
+ # Try to recover some text from data if text is empty
64
+ if not text and isinstance(data, dict):
65
+ text = data.get("text") or data.get("message") or data.get("summary")
66
+
67
+ if not text:
68
+ continue
69
+
70
+ # Keep it short-ish; you can truncate here if needed
71
+ lines.append(f"[{kind}] {text}")
72
+ recent_events = "\n".join(lines)
73
+ except Exception as e:
74
+ logger = getattr(context, "logger", None)
75
+ if logger:
76
+ logger.warning(
77
+ "build_session_memory_prompt_segments: mem.recent failed",
78
+ extra={"error": str(e)},
79
+ )
80
+
81
+ return session_summary, recent_events