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
@@ -0,0 +1,315 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from aethergraph.contracts.services.memory import Event
6
+
7
+ if TYPE_CHECKING:
8
+ from .types import MemoryFacadeInterface
9
+
10
+
11
+ class ResultMixin:
12
+ """Methods for recording tool execution results.
13
+ NOTE: there are many potentially overlapping methods here. We will deprecate most of them
14
+ over time in favor of a smaller, clearer set.
15
+
16
+ Include methods:
17
+ - write_result (general)
18
+ - write_tool_result (deprecated, use record_tool_result)
19
+ - record_result (general alias)
20
+ - record_tool_result (dedicated to tools)
21
+ - last_tool_result
22
+ - recent_tool_result_data
23
+
24
+ The following are convenience wrappers. TODO: standardize naming
25
+ - last_by_name
26
+ - last_output_by_name
27
+ - last_outputs_by_topic
28
+ - last_tool_result_outputs
29
+ - latest_refs_by_kind
30
+ """
31
+
32
+ async def record_tool_result(
33
+ self: MemoryFacadeInterface,
34
+ *,
35
+ tool: str,
36
+ inputs: list[dict[str, Any]] | None = None,
37
+ outputs: list[dict[str, Any]] | None = None,
38
+ tags: list[str] | None = None,
39
+ metrics: dict[str, float] | None = None,
40
+ message: str | None = None,
41
+ severity: int = 3,
42
+ ) -> Event:
43
+ """
44
+ Record the result of a tool execution in a normalized format.
45
+
46
+ This method provides the method to log tool execution results with standardized metadata.
47
+ Interally, it constructs an `Event` object encapsulating details about the tool execution,
48
+ including inputs, outputs, tags, metrics, and a descriptive message.
49
+
50
+ Examples:
51
+ Recording a tool result with inputs and outputs:
52
+ ```python
53
+ await context.memory().record_tool_result(
54
+ tool="data_cleaner",
55
+ inputs=[{"raw_data": "some raw input"}],
56
+ outputs=[{"cleaned_data": "processed output"}],
57
+ tags=["data", "cleaning"],
58
+ metrics={"execution_time": 1.23},
59
+ message="Tool executed successfully.",
60
+ severity=2,
61
+ )
62
+ ```
63
+
64
+ Logging a tool result with minimal metadata:
65
+ ```python
66
+ await context.memory().record_tool_result(
67
+ tool="simple_logger",
68
+ message="Logged an event.",
69
+ )
70
+ ```
71
+
72
+ Args:
73
+ tool: The name of the tool that generated the result.
74
+ inputs: A list of dictionaries representing the tool's input data.
75
+ outputs: A list of dictionaries representing the tool's output data.
76
+ tags: A list of string labels for categorization.
77
+ metrics: A dictionary of numerical metrics (e.g., execution time, accuracy).
78
+ message: A descriptive message about the tool's execution or result.
79
+ severity: An integer (1-5) indicating the importance or severity of the result.
80
+ (1=Lowest, 5=Highest).
81
+
82
+ Returns:
83
+ Event: The fully persisted `Event` object containing the generated ID and timestamp.
84
+ """
85
+ return await self.write_tool_result(
86
+ tool=tool,
87
+ inputs=inputs,
88
+ outputs=outputs,
89
+ tags=tags,
90
+ metrics=metrics,
91
+ message=message,
92
+ severity=severity,
93
+ )
94
+
95
+ async def recent_tool_results(
96
+ self,
97
+ *,
98
+ tool: str,
99
+ limit: int = 10,
100
+ ) -> list[Event]:
101
+ """
102
+ Retrieve recent tool execution results for a specific tool.
103
+
104
+ This method filters and returns the most recent `tool_result` events
105
+ associated with the specified tool, allowing you to analyze or process
106
+ the results of tool executions.
107
+
108
+ Examples:
109
+ Fetching the 5 most recent results for a tool:
110
+ ```python
111
+ recent_results = await context.memory().recent_tool_results(
112
+ tool="data_cleaner",
113
+ limit=5,
114
+ )
115
+ for result in recent_results:
116
+ print(result)
117
+ ```
118
+
119
+ Retrieving all available results for a tool (up to the default limit):
120
+ ```python
121
+ recent_results = await context.memory().recent_tool_results(
122
+ tool="simple_logger",
123
+ )
124
+ ```
125
+
126
+ Args:
127
+ tool: The name of the tool whose results are being queried.
128
+ limit: The maximum number of results to return (default is 10).
129
+
130
+ Returns:
131
+ list[Event]: A list of `Event` objects representing the recent
132
+ `tool_result` events for the specified tool, ordered by recency.
133
+ """
134
+ events = await self.recent(kinds=["tool_result"], limit=100)
135
+ tool_events = [e for e in events if e.tool == tool]
136
+ return tool_events[:limit]
137
+
138
+ async def recent_tool_result_data(
139
+ self,
140
+ *,
141
+ tool: str,
142
+ limit: int = 10,
143
+ ) -> list[dict[str, Any]]:
144
+ """
145
+ Return a simplified view over recent tool_result events.
146
+
147
+ This method provides a developer-friendly way to retrieve recent tool execution results
148
+ in a normalized format, including metadata such as timestamps, inputs, outputs, and tags.
149
+
150
+ Examples:
151
+ Fetching recent tool result data:
152
+ ```python
153
+ recent_data = await context.memory().recent_tool_result_data(
154
+ tool="data_cleaner",
155
+ limit=5,
156
+ )
157
+ for entry in recent_data:
158
+ print(entry)
159
+ ```
160
+
161
+ Args:
162
+ tool: The name of the tool whose results are being queried.
163
+ limit: The maximum number of recent results to retrieve.
164
+
165
+ Returns:
166
+ list[dict[str, Any]]: A list of dictionaries, each containing:
167
+ - "ts": The timestamp of the event.
168
+ - "tool": The name of the tool.
169
+ - "message": A descriptive message about the tool's execution.
170
+ - "inputs": The input data provided to the tool.
171
+ - "outputs": The output data generated by the tool.
172
+ - "tags": A list of string labels associated with the event.
173
+ """
174
+ events = await self.recent_tool_results(tool=tool, limit=limit)
175
+ out: list[dict[str, Any]] = []
176
+ for e in events:
177
+ out.append(
178
+ {
179
+ "ts": getattr(e, "ts", None),
180
+ "tool": e.tool,
181
+ "message": e.text,
182
+ "inputs": getattr(e, "inputs", None),
183
+ "outputs": getattr(e, "outputs", None),
184
+ "tags": list(e.tags or []),
185
+ }
186
+ )
187
+ return out
188
+
189
+ async def write_result(
190
+ self: MemoryFacadeInterface,
191
+ *,
192
+ tool: str | None = None, # back compatibility with 'topic'
193
+ inputs: list[dict[str, Any]] | None = None,
194
+ outputs: list[dict[str, Any]] | None = None,
195
+ tags: list[str] | None = None,
196
+ metrics: dict[str, float] | None = None,
197
+ message: str | None = None,
198
+ severity: int = 3,
199
+ topic: str | None = None, # alias for tool, backwards compatibility
200
+ ) -> Event:
201
+ """
202
+ Convenience for recording a “tool/agent/flow result” with typed I/O.
203
+
204
+ `tool` : tool/agent/flow identifier (also used by KVIndices.last_outputs_by_topic)
205
+ `inputs` : List[Value]-like dicts
206
+ `outputs` : List[Value]-like dicts
207
+ `tags` : labels like ["rag","qa"] for filtering/search
208
+ """
209
+ if tool is None and topic is not None:
210
+ tool = topic
211
+ if tool is None:
212
+ raise ValueError("write_result requires a 'tool' (or legacy 'topic') name")
213
+
214
+ inputs = inputs or []
215
+ outputs = outputs or []
216
+
217
+ evt = await self.record_raw(
218
+ base=dict(
219
+ tool=tool,
220
+ kind="tool_result",
221
+ severity=severity,
222
+ tags=tags or [],
223
+ inputs=inputs,
224
+ outputs=outputs,
225
+ ),
226
+ text=message,
227
+ metrics=metrics,
228
+ )
229
+ await self.indices.update(self.timeline_id, evt)
230
+ return evt
231
+
232
+ async def write_tool_result(
233
+ self: MemoryFacadeInterface,
234
+ *,
235
+ tool: str,
236
+ inputs: list[dict[str, Any]] | None = None,
237
+ outputs: list[dict[str, Any]] | None = None,
238
+ tags: list[str] | None = None,
239
+ metrics: dict[str, float] | None = None,
240
+ message: str | None = None,
241
+ severity: int = 3,
242
+ ) -> Event:
243
+ """
244
+ Convenience wrapper around write_result() for tool results.
245
+ """
246
+ return await self.write_result(
247
+ tool=tool,
248
+ inputs=inputs,
249
+ outputs=outputs,
250
+ tags=tags,
251
+ metrics=metrics,
252
+ message=message,
253
+ severity=severity,
254
+ )
255
+
256
+ async def record_result(
257
+ self: MemoryFacadeInterface,
258
+ *,
259
+ tool: str | None = None,
260
+ inputs: list[dict[str, Any]] | None = None,
261
+ outputs: list[dict[str, Any]] | None = None,
262
+ tags: list[str] | None = None,
263
+ metrics: dict[str, float] | None = None,
264
+ message: str | None = None,
265
+ severity: int = 3,
266
+ ) -> Event:
267
+ """
268
+ Alias for write_result(); symmetric with record_tool_result().
269
+
270
+ Use this when you conceptually have a "result" but don't care whether
271
+ it's a tool vs agent vs flow.
272
+ """
273
+ return await self.write_result(
274
+ tool=tool,
275
+ inputs=inputs,
276
+ outputs=outputs,
277
+ tags=tags,
278
+ metrics=metrics,
279
+ message=message,
280
+ severity=severity,
281
+ )
282
+
283
+ async def last_tool_result(self, tool: str) -> Event | None:
284
+ """
285
+ Convenience: return the most recent tool_result Event for a given tool.
286
+ """
287
+ events = await self.recent_tool_results(tool=tool, limit=1)
288
+ return events[-1] if events else None
289
+
290
+ async def last_by_name(self, name: str):
291
+ """Return the last output value by `name` from Indices (fast path)."""
292
+ return await self.indices.last_by_name(self.timeline_id, name)
293
+
294
+ async def last_output_by_name(self, name: str):
295
+ """Return the last output value (Value.value) by `name` from Indices (fast path)."""
296
+ out = await self.indices.last_by_name(self.timeline_id, name)
297
+ if out is None:
298
+ return None
299
+ return out.get("value") # type: ignore
300
+
301
+ async def last_outputs_by_topic(self, topic: str):
302
+ """Return the last output map for a given topic (tool/flow/agent) from Indices."""
303
+ return await self.indices.last_outputs_by_topic(self.timeline_id, topic)
304
+
305
+ # replace last_tool_result_outputs
306
+ async def last_tool_result_outputs(self, tool: str) -> dict[str, Any] | None:
307
+ """
308
+ Convenience wrapper around KVIndices.last_outputs_by_topic for this run.
309
+ Returns the last outputs map for a given tool, or None.
310
+ """
311
+ return await self.indices.last_outputs_by_topic(self.timeline_id, tool)
312
+
313
+ async def latest_refs_by_kind(self, kind: str, *, limit: int = 50):
314
+ """Return latest ref outputs by ref.kind (fast path, KV-backed)."""
315
+ return await self.indices.latest_refs_by_kind(self.timeline_id, kind, limit=limit)
@@ -0,0 +1,139 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ if TYPE_CHECKING:
7
+ from aethergraph.contracts.services.memory import Event
8
+
9
+ from .types import MemoryFacadeInterface
10
+
11
+
12
+ class RetrievalMixin:
13
+ """Methods for retrieving events and values."""
14
+
15
+ async def recent(
16
+ self: MemoryFacadeInterface, *, kinds: list[str] | None = None, limit: int = 50
17
+ ) -> list[Event]:
18
+ """
19
+ Retrieve recent events.
20
+
21
+ This method fetches a list of recent events, optionally filtered by kinds.
22
+
23
+ Args:
24
+ kinds: A list of event kinds to filter by. Defaults to None.
25
+ limit: The maximum number of events to retrieve. Defaults to 50.
26
+
27
+ Returns:
28
+ list[Event]: A list of recent events.
29
+
30
+ Notes:
31
+ This method interacts with the underlying HotLog service to fetch events
32
+ associated with the current timeline. The events are returned in chronological order,
33
+ with the most recent events appearing last in the list. Memory out of the limit will be discarded
34
+ in the HotLog layer (but persistent in the Persistence layer). Memory in persistence cannot be retrieved
35
+ via this method.
36
+ """
37
+ return await self.hotlog.recent(self.timeline_id, kinds=kinds, limit=limit)
38
+
39
+ async def recent_data(
40
+ self: MemoryFacadeInterface,
41
+ *,
42
+ kinds: list[str] | None = None,
43
+ tags: list[str] | None = None,
44
+ limit: int = 50,
45
+ ) -> list[Any]:
46
+ """
47
+ Retrieve recent event data.
48
+
49
+ This method fetches the data or text of recent events, optionally filtered by kinds and tags.
50
+ Unlike `recent()`, which returns full Event objects, this method extracts and returns only the
51
+ data or text content of the events. This is useful for scenarios where only the event payloads are needed.
52
+
53
+ Args:
54
+ kinds: A list of event kinds to filter by. Defaults to None.
55
+ tags: A list of tags to filter events by. Defaults to None.
56
+ limit: The maximum number of events to retrieve. Defaults to 50.
57
+
58
+ Returns:
59
+ list[Any]: A list of event data or text.
60
+
61
+ Notes:
62
+ This method first retrieves recent events using the `recent()` method and then filters them
63
+ based on the provided tags. It extracts the `data` attribute if available; otherwise, it
64
+ attempts to parse the `text` attribute as JSON. If parsing fails, the raw text is returned.
65
+
66
+ Memory out of the limit will be discarded in the HotLog layer (but persistent in the Persistence layer).
67
+ Memory in persistence cannot be retrieved via this method.
68
+ """
69
+ evts = await self.recent(kinds=kinds, limit=limit)
70
+ if tags:
71
+ want = set(tags)
72
+ evts = [e for e in evts if want.issubset(set(e.tags or []))]
73
+
74
+ out: list[Any] = []
75
+ for e in evts:
76
+ if e.data is not None:
77
+ out.append(e.data)
78
+ elif e.text:
79
+ t = e.text.strip()
80
+ if (t.startswith("{") and t.endswith("}")) or (
81
+ t.startswith("[") and t.endswith("]")
82
+ ):
83
+ try:
84
+ out.append(json.loads(t))
85
+ continue
86
+ except Exception:
87
+ pass
88
+ out.append(e.text)
89
+ return out
90
+
91
+ async def search(
92
+ self: MemoryFacadeInterface,
93
+ *,
94
+ query: str,
95
+ kinds: list[str] | None = None,
96
+ tags: list[str] | None = None,
97
+ limit: int = 100,
98
+ use_embedding: bool = True,
99
+ ) -> list[Event]:
100
+ """
101
+ Search for events based on a query.
102
+
103
+ This method searches for events that match a query, optionally filtered by kinds and tags.
104
+ Note that this implementation currently performs a lexical search. Embedding-based search
105
+ is planned for future development.
106
+
107
+ Args:
108
+ query: The search query string.
109
+ kinds: A list of event kinds to filter by. Defaults to None.
110
+ tags: A list of tags to filter events by. Defaults to None.
111
+ limit: The maximum number of events to retrieve. Defaults to 100.
112
+ use_embedding: Whether to use embedding-based search. Defaults to True.
113
+
114
+ Returns:
115
+ list[Event]: A list of events matching the query.
116
+
117
+ Notes:
118
+ This method retrieves recent events using the `recent()` method and filters them
119
+ based on the provided tags. It performs a simple lexical search on the event text.
120
+ Embedding-based search functionality is not yet implemented.
121
+
122
+ Memory out of the limit will be discarded in the HotLog layer (but persistent in the Persistence layer).
123
+ Memory in persistence cannot be retrieved via this method.
124
+ """
125
+ events = await self.recent(kinds=kinds, limit=limit)
126
+ if tags:
127
+ want = set(tags)
128
+ events = [e for e in events if want.issubset(set(e.tags or []))]
129
+
130
+ query_l = query.lower()
131
+ lexical_hits = [e for e in events if (e.text or "").lower().find(query_l) >= 0]
132
+
133
+ if not use_embedding:
134
+ return lexical_hits or events
135
+
136
+ # Placeholder for future embedding search logic
137
+ # if not (self.llm and any(e.embedding for e in events)): return lexical_hits or events
138
+ # ... logic ...
139
+ return lexical_hits or events
@@ -0,0 +1,77 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Protocol
4
+
5
+ from aethergraph.contracts.services.llm import LLMClientProtocol
6
+ from aethergraph.contracts.services.memory import Event, HotLog, Indices, Persistence
7
+ from aethergraph.contracts.storage.artifact_store import AsyncArtifactStore
8
+ from aethergraph.contracts.storage.doc_store import DocStore
9
+ from aethergraph.services.rag.facade import RAGFacade
10
+ from aethergraph.services.scope.scope import Scope
11
+
12
+
13
+ class MemoryFacadeInterface(Protocol):
14
+ """
15
+ Protocol defining the state and core methods available on the MemoryFacade.
16
+ Mixins use this to type-hint 'self'.
17
+ """
18
+
19
+ run_id: str
20
+ timeline_id: str
21
+ memory_scope_id: str
22
+
23
+ hotlog: HotLog
24
+ persistence: Persistence
25
+ indices: Indices
26
+ docs: DocStore
27
+ artifacts: AsyncArtifactStore
28
+ scope: Scope | None
29
+
30
+ rag: RAGFacade | None
31
+ llm: LLMClientProtocol | None
32
+ logger: Any
33
+
34
+ default_signal_threshold: float
35
+ hot_limit: int
36
+ hot_ttl_s: int
37
+
38
+ async def record_raw(
39
+ self,
40
+ *,
41
+ base: dict[str, Any],
42
+ text: str | None = None,
43
+ metrics: dict[str, float] | None = None,
44
+ ) -> Event: ...
45
+
46
+ async def record(
47
+ self,
48
+ kind: str,
49
+ data: Any,
50
+ tags: list[str] | None = None,
51
+ severity: int = 2,
52
+ stage: str | None = None,
53
+ inputs_ref=None,
54
+ outputs_ref=None,
55
+ metrics: dict[str, float] | None = None,
56
+ signal: float | None = None,
57
+ ) -> Event: ...
58
+
59
+ async def write_result(
60
+ self,
61
+ *,
62
+ tool: str,
63
+ inputs: list[dict[str, Any]] | None = None,
64
+ outputs: list[dict[str, Any]] | None = None,
65
+ tags: list[str] | None = None,
66
+ metrics: dict[str, float] | None = None,
67
+ message: str | None = None,
68
+ severity: int = 3,
69
+ ) -> Event: ...
70
+
71
+ # Required for RetrievalMixin to expose 'recent' to other mixins
72
+ async def recent(self, *, kinds: list[str] | None = None, limit: int = 50) -> list[Event]: ...
73
+
74
+ # Required for RAGMixin to expose 'rag_bind'
75
+ async def rag_bind(
76
+ self, *, key: str = "default", create_if_missing: bool = True, labels: dict | None = None
77
+ ) -> str: ...
@@ -0,0 +1,43 @@
1
+ import hashlib
2
+ import json
3
+ import os
4
+ import re
5
+ import time
6
+ from typing import Any
7
+ import unicodedata
8
+
9
+ _SAFE = re.compile(r"[^A-Za-z0-9._-]+")
10
+
11
+
12
+ def now_iso() -> str:
13
+ return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
14
+
15
+
16
+ def stable_event_id(parts: dict[str, Any]) -> str:
17
+ blob = json.dumps(parts, sort_keys=True, ensure_ascii=False).encode("utf-8")
18
+ return hashlib.sha256(blob).hexdigest()[:24]
19
+
20
+
21
+ def short_hash(s: str, n: int = 8) -> str:
22
+ return hashlib.sha256(s.encode("utf-8")).hexdigest()[:n]
23
+
24
+
25
+ def slug(s: str) -> str:
26
+ s = unicodedata.normalize("NFKC", str(s)).strip()
27
+ s = s.replace(" ", "-")
28
+ s = _SAFE.sub("-", s)
29
+ return s.strip("-") or "default"
30
+
31
+
32
+ def load_sticky(path: str) -> dict:
33
+ try:
34
+ with open(path, encoding="utf-8") as f:
35
+ return json.load(f)
36
+ except Exception:
37
+ return {}
38
+
39
+
40
+ def save_sticky(path: str, m: dict):
41
+ os.makedirs(os.path.dirname(path), exist_ok=True)
42
+ with open(path, "w", encoding="utf-8") as f:
43
+ json.dump(m, f, ensure_ascii=False, indent=2)