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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. aethergraph/__init__.py +4 -10
  2. aethergraph/__main__.py +293 -0
  3. aethergraph/api/v1/__init__.py +0 -0
  4. aethergraph/api/v1/agents.py +46 -0
  5. aethergraph/api/v1/apps.py +70 -0
  6. aethergraph/api/v1/artifacts.py +415 -0
  7. aethergraph/api/v1/channels.py +89 -0
  8. aethergraph/api/v1/deps.py +168 -0
  9. aethergraph/api/v1/graphs.py +259 -0
  10. aethergraph/api/v1/identity.py +25 -0
  11. aethergraph/api/v1/memory.py +353 -0
  12. aethergraph/api/v1/misc.py +47 -0
  13. aethergraph/api/v1/pagination.py +29 -0
  14. aethergraph/api/v1/runs.py +568 -0
  15. aethergraph/api/v1/schemas.py +535 -0
  16. aethergraph/api/v1/session.py +323 -0
  17. aethergraph/api/v1/stats.py +201 -0
  18. aethergraph/api/v1/viz.py +152 -0
  19. aethergraph/config/config.py +22 -0
  20. aethergraph/config/loader.py +3 -2
  21. aethergraph/config/storage.py +209 -0
  22. aethergraph/contracts/__init__.py +0 -0
  23. aethergraph/contracts/services/__init__.py +0 -0
  24. aethergraph/contracts/services/artifacts.py +27 -14
  25. aethergraph/contracts/services/memory.py +45 -17
  26. aethergraph/contracts/services/metering.py +129 -0
  27. aethergraph/contracts/services/runs.py +50 -0
  28. aethergraph/contracts/services/sessions.py +87 -0
  29. aethergraph/contracts/services/state_stores.py +3 -0
  30. aethergraph/contracts/services/viz.py +44 -0
  31. aethergraph/contracts/storage/artifact_index.py +88 -0
  32. aethergraph/contracts/storage/artifact_store.py +99 -0
  33. aethergraph/contracts/storage/async_kv.py +34 -0
  34. aethergraph/contracts/storage/blob_store.py +50 -0
  35. aethergraph/contracts/storage/doc_store.py +35 -0
  36. aethergraph/contracts/storage/event_log.py +31 -0
  37. aethergraph/contracts/storage/vector_index.py +48 -0
  38. aethergraph/core/__init__.py +0 -0
  39. aethergraph/core/execution/forward_scheduler.py +13 -2
  40. aethergraph/core/execution/global_scheduler.py +21 -15
  41. aethergraph/core/execution/step_forward.py +10 -1
  42. aethergraph/core/graph/__init__.py +0 -0
  43. aethergraph/core/graph/graph_builder.py +8 -4
  44. aethergraph/core/graph/graph_fn.py +156 -15
  45. aethergraph/core/graph/graph_spec.py +8 -0
  46. aethergraph/core/graph/graphify.py +146 -27
  47. aethergraph/core/graph/node_spec.py +0 -2
  48. aethergraph/core/graph/node_state.py +3 -0
  49. aethergraph/core/graph/task_graph.py +39 -1
  50. aethergraph/core/runtime/__init__.py +0 -0
  51. aethergraph/core/runtime/ad_hoc_context.py +64 -4
  52. aethergraph/core/runtime/base_service.py +28 -4
  53. aethergraph/core/runtime/execution_context.py +13 -15
  54. aethergraph/core/runtime/graph_runner.py +222 -37
  55. aethergraph/core/runtime/node_context.py +510 -6
  56. aethergraph/core/runtime/node_services.py +12 -5
  57. aethergraph/core/runtime/recovery.py +15 -1
  58. aethergraph/core/runtime/run_manager.py +783 -0
  59. aethergraph/core/runtime/run_manager_local.py +204 -0
  60. aethergraph/core/runtime/run_registration.py +2 -2
  61. aethergraph/core/runtime/run_types.py +89 -0
  62. aethergraph/core/runtime/runtime_env.py +136 -7
  63. aethergraph/core/runtime/runtime_metering.py +71 -0
  64. aethergraph/core/runtime/runtime_registry.py +36 -13
  65. aethergraph/core/runtime/runtime_services.py +194 -6
  66. aethergraph/core/tools/builtins/toolset.py +1 -1
  67. aethergraph/core/tools/toolkit.py +5 -0
  68. aethergraph/plugins/agents/default_chat_agent copy.py +90 -0
  69. aethergraph/plugins/agents/default_chat_agent.py +171 -0
  70. aethergraph/plugins/agents/shared.py +81 -0
  71. aethergraph/plugins/channel/adapters/webui.py +112 -112
  72. aethergraph/plugins/channel/routes/webui_routes.py +367 -102
  73. aethergraph/plugins/channel/utils/slack_utils.py +115 -59
  74. aethergraph/plugins/channel/utils/telegram_utils.py +88 -47
  75. aethergraph/plugins/channel/websockets/weibui_ws.py +172 -0
  76. aethergraph/runtime/__init__.py +15 -0
  77. aethergraph/server/app_factory.py +190 -34
  78. aethergraph/server/clients/channel_client.py +202 -0
  79. aethergraph/server/http/channel_http_routes.py +116 -0
  80. aethergraph/server/http/channel_ws_routers.py +45 -0
  81. aethergraph/server/loading.py +117 -0
  82. aethergraph/server/server.py +131 -0
  83. aethergraph/server/server_state.py +240 -0
  84. aethergraph/server/start.py +227 -66
  85. aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  86. aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  87. aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  88. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  89. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  90. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  91. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  92. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  93. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  94. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  95. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  96. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  97. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  98. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  99. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  100. aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  101. aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  102. aethergraph/server/ui_static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  103. aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  104. aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  105. aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  106. aethergraph/server/ui_static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  107. aethergraph/server/ui_static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  108. aethergraph/server/ui_static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  109. aethergraph/server/ui_static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  110. aethergraph/server/ui_static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  111. aethergraph/server/ui_static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  112. aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  113. aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  114. aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  115. aethergraph/server/ui_static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  116. aethergraph/server/ui_static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  117. aethergraph/server/ui_static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  118. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  119. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  120. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  121. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  122. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  123. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  124. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  125. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  126. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  127. aethergraph/server/ui_static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  128. aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  129. aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  130. aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  131. aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  132. aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  133. aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  134. aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  135. aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  136. aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  137. aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  138. aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  139. aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  140. aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  141. aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  142. aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  143. aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  144. aethergraph/server/ui_static/assets/index-BR5GtXcZ.css +1 -0
  145. aethergraph/server/ui_static/assets/index-CQ0HZZ83.js +400 -0
  146. aethergraph/server/ui_static/index.html +15 -0
  147. aethergraph/server/ui_static/logo.png +0 -0
  148. aethergraph/services/artifacts/__init__.py +0 -0
  149. aethergraph/services/artifacts/facade.py +1239 -132
  150. aethergraph/services/auth/{dev.py → authn.py} +0 -8
  151. aethergraph/services/auth/authz.py +100 -0
  152. aethergraph/services/channel/__init__.py +0 -0
  153. aethergraph/services/channel/channel_bus.py +19 -1
  154. aethergraph/services/channel/factory.py +13 -1
  155. aethergraph/services/channel/ingress.py +311 -0
  156. aethergraph/services/channel/queue_adapter.py +75 -0
  157. aethergraph/services/channel/session.py +502 -19
  158. aethergraph/services/container/default_container.py +122 -43
  159. aethergraph/services/continuations/continuation.py +6 -0
  160. aethergraph/services/continuations/stores/fs_store.py +19 -0
  161. aethergraph/services/eventhub/event_hub.py +76 -0
  162. aethergraph/services/kv/__init__.py +0 -0
  163. aethergraph/services/kv/ephemeral.py +244 -0
  164. aethergraph/services/llm/__init__.py +0 -0
  165. aethergraph/services/llm/generic_client copy.py +691 -0
  166. aethergraph/services/llm/generic_client.py +1288 -187
  167. aethergraph/services/llm/providers.py +3 -1
  168. aethergraph/services/llm/types.py +47 -0
  169. aethergraph/services/llm/utils.py +284 -0
  170. aethergraph/services/logger/std.py +3 -0
  171. aethergraph/services/mcp/__init__.py +9 -0
  172. aethergraph/services/mcp/http_client.py +38 -0
  173. aethergraph/services/mcp/service.py +225 -1
  174. aethergraph/services/mcp/stdio_client.py +41 -6
  175. aethergraph/services/mcp/ws_client.py +44 -2
  176. aethergraph/services/memory/__init__.py +0 -0
  177. aethergraph/services/memory/distillers/llm_long_term.py +234 -0
  178. aethergraph/services/memory/distillers/llm_meta_summary.py +398 -0
  179. aethergraph/services/memory/distillers/long_term.py +225 -0
  180. aethergraph/services/memory/facade/__init__.py +3 -0
  181. aethergraph/services/memory/facade/chat.py +440 -0
  182. aethergraph/services/memory/facade/core.py +447 -0
  183. aethergraph/services/memory/facade/distillation.py +424 -0
  184. aethergraph/services/memory/facade/rag.py +410 -0
  185. aethergraph/services/memory/facade/results.py +315 -0
  186. aethergraph/services/memory/facade/retrieval.py +139 -0
  187. aethergraph/services/memory/facade/types.py +77 -0
  188. aethergraph/services/memory/facade/utils.py +43 -0
  189. aethergraph/services/memory/facade_dep.py +1539 -0
  190. aethergraph/services/memory/factory.py +9 -3
  191. aethergraph/services/memory/utils.py +10 -0
  192. aethergraph/services/metering/eventlog_metering.py +470 -0
  193. aethergraph/services/metering/noop.py +25 -4
  194. aethergraph/services/rag/__init__.py +0 -0
  195. aethergraph/services/rag/facade.py +279 -23
  196. aethergraph/services/rag/index_factory.py +2 -2
  197. aethergraph/services/rag/node_rag.py +317 -0
  198. aethergraph/services/rate_limit/inmem_rate_limit.py +24 -0
  199. aethergraph/services/registry/__init__.py +0 -0
  200. aethergraph/services/registry/agent_app_meta.py +419 -0
  201. aethergraph/services/registry/registry_key.py +1 -1
  202. aethergraph/services/registry/unified_registry.py +74 -6
  203. aethergraph/services/scope/scope.py +159 -0
  204. aethergraph/services/scope/scope_factory.py +164 -0
  205. aethergraph/services/state_stores/serialize.py +5 -0
  206. aethergraph/services/state_stores/utils.py +2 -1
  207. aethergraph/services/viz/__init__.py +0 -0
  208. aethergraph/services/viz/facade.py +413 -0
  209. aethergraph/services/viz/viz_service.py +69 -0
  210. aethergraph/storage/artifacts/artifact_index_jsonl.py +180 -0
  211. aethergraph/storage/artifacts/artifact_index_sqlite.py +426 -0
  212. aethergraph/storage/artifacts/cas_store.py +422 -0
  213. aethergraph/storage/artifacts/fs_cas.py +18 -0
  214. aethergraph/storage/artifacts/s3_cas.py +14 -0
  215. aethergraph/storage/artifacts/utils.py +124 -0
  216. aethergraph/storage/blob/fs_blob.py +86 -0
  217. aethergraph/storage/blob/s3_blob.py +115 -0
  218. aethergraph/storage/continuation_store/fs_cont.py +283 -0
  219. aethergraph/storage/continuation_store/inmem_cont.py +146 -0
  220. aethergraph/storage/continuation_store/kvdoc_cont.py +261 -0
  221. aethergraph/storage/docstore/fs_doc.py +63 -0
  222. aethergraph/storage/docstore/sqlite_doc.py +31 -0
  223. aethergraph/storage/docstore/sqlite_doc_sync.py +90 -0
  224. aethergraph/storage/eventlog/fs_event.py +136 -0
  225. aethergraph/storage/eventlog/sqlite_event.py +47 -0
  226. aethergraph/storage/eventlog/sqlite_event_sync.py +178 -0
  227. aethergraph/storage/factory.py +432 -0
  228. aethergraph/storage/fs_utils.py +28 -0
  229. aethergraph/storage/graph_state_store/state_store.py +64 -0
  230. aethergraph/storage/kv/inmem_kv.py +103 -0
  231. aethergraph/storage/kv/layered_kv.py +52 -0
  232. aethergraph/storage/kv/sqlite_kv.py +39 -0
  233. aethergraph/storage/kv/sqlite_kv_sync.py +98 -0
  234. aethergraph/storage/memory/event_persist.py +68 -0
  235. aethergraph/storage/memory/fs_persist.py +118 -0
  236. aethergraph/{services/memory/hotlog_kv.py → storage/memory/hotlog.py} +8 -2
  237. aethergraph/{services → storage}/memory/indices.py +31 -7
  238. aethergraph/storage/metering/meter_event.py +55 -0
  239. aethergraph/storage/runs/doc_store.py +280 -0
  240. aethergraph/storage/runs/inmen_store.py +82 -0
  241. aethergraph/storage/runs/sqlite_run_store.py +403 -0
  242. aethergraph/storage/sessions/doc_store.py +183 -0
  243. aethergraph/storage/sessions/inmem_store.py +110 -0
  244. aethergraph/storage/sessions/sqlite_session_store.py +399 -0
  245. aethergraph/storage/vector_index/chroma_index.py +138 -0
  246. aethergraph/storage/vector_index/faiss_index.py +179 -0
  247. aethergraph/storage/vector_index/sqlite_index.py +187 -0
  248. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/METADATA +138 -31
  249. aethergraph-0.1.0a2.dist-info/RECORD +356 -0
  250. aethergraph-0.1.0a2.dist-info/entry_points.txt +3 -0
  251. aethergraph/services/artifacts/factory.py +0 -35
  252. aethergraph/services/artifacts/fs_store.py +0 -656
  253. aethergraph/services/artifacts/jsonl_index.py +0 -123
  254. aethergraph/services/artifacts/sqlite_index.py +0 -209
  255. aethergraph/services/memory/distillers/episode.py +0 -116
  256. aethergraph/services/memory/distillers/rolling.py +0 -74
  257. aethergraph/services/memory/facade.py +0 -633
  258. aethergraph/services/memory/persist_fs.py +0 -40
  259. aethergraph/services/rag/index/base.py +0 -27
  260. aethergraph/services/rag/index/faiss_index.py +0 -121
  261. aethergraph/services/rag/index/sqlite_index.py +0 -134
  262. aethergraph-0.1.0a1.dist-info/RECORD +0 -182
  263. aethergraph-0.1.0a1.dist-info/entry_points.txt +0 -2
  264. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/WHEEL +0 -0
  265. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/licenses/LICENSE +0 -0
  266. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/licenses/NOTICE +0 -0
  267. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,447 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import logging
5
+ from typing import Any
6
+
7
+ from aethergraph.contracts.services.llm import LLMClientProtocol
8
+ from aethergraph.contracts.services.memory import Event, HotLog, Indices, Persistence
9
+ from aethergraph.contracts.storage.artifact_store import AsyncArtifactStore
10
+ from aethergraph.contracts.storage.doc_store import DocStore
11
+ from aethergraph.core.runtime.runtime_metering import current_metering
12
+ from aethergraph.services.rag.facade import RAGFacade
13
+ from aethergraph.services.scope.scope import Scope
14
+
15
+ from .chat import ChatMixin
16
+ from .distillation import DistillationMixin
17
+ from .rag import RAGMixin
18
+ from .results import ResultMixin
19
+ from .retrieval import RetrievalMixin
20
+ from .utils import now_iso, stable_event_id
21
+
22
+
23
+ class MemoryFacade(ChatMixin, ResultMixin, RetrievalMixin, DistillationMixin, RAGMixin):
24
+ """
25
+ MemoryFacade coordinates core memory services for a specific run/session.
26
+ Functionality is split across mixins in the `facade/` directory.
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ *,
32
+ run_id: str,
33
+ session_id: str | None,
34
+ graph_id: str | None,
35
+ node_id: str | None,
36
+ scope: Scope | None = None,
37
+ hotlog: HotLog,
38
+ persistence: Persistence,
39
+ indices: Indices,
40
+ docs: DocStore,
41
+ artifact_store: AsyncArtifactStore,
42
+ hot_limit: int = 1000,
43
+ hot_ttl_s: int = 7 * 24 * 3600,
44
+ default_signal_threshold: float = 0.0,
45
+ logger=None,
46
+ rag: RAGFacade | None = None,
47
+ llm: LLMClientProtocol | None = None,
48
+ ):
49
+ self.run_id = run_id
50
+ self.session_id = session_id
51
+ self.graph_id = graph_id
52
+ self.node_id = node_id
53
+ self.scope = scope
54
+ self.hotlog = hotlog
55
+ self.persistence = persistence
56
+ self.indices = indices
57
+ self.docs = docs
58
+ self.artifacts = artifact_store
59
+ self.hot_limit = hot_limit
60
+ self.hot_ttl_s = hot_ttl_s
61
+ self.default_signal_threshold = default_signal_threshold
62
+ self.logger = logger or logging.getLogger(__name__)
63
+ self.rag = rag
64
+ self.llm = llm
65
+
66
+ self.memory_scope_id = (
67
+ self.scope.memory_scope_id() if self.scope else self.session_id or self.run_id
68
+ )
69
+ self.timeline_id = self.memory_scope_id or self.run_id
70
+
71
+ async def record_raw(
72
+ self,
73
+ *,
74
+ base: dict[str, Any],
75
+ text: str | None = None,
76
+ metrics: dict[str, float] | None = None,
77
+ ) -> Event:
78
+ """
79
+ Record an unstructured event with optional preview text and metrics.
80
+
81
+ This method generates a stable event ID, populates standard fields
82
+ (e.g., `run_id`, `scope_id`, `severity`, `signal`), and appends the
83
+ event to both the HotLog and Persistence layers. Additionally, it
84
+ records a metering event for tracking purposes.
85
+
86
+ Examples:
87
+ Basic usage with minimal fields:
88
+ ```python
89
+ await context.memory().record_raw(
90
+ base={"kind": "user_action", "severity": 2},
91
+ text="User clicked a button."
92
+ )
93
+ ```
94
+
95
+ Including metrics and additional fields:
96
+ ```python
97
+ await context.memory().record_raw(
98
+ base={"kind": "tool_call", "stage": "execution", "severity": 3},
99
+ text="Tool executed successfully.",
100
+ metrics={"latency": 0.123, "tokens_used": 45}
101
+ )
102
+ ```
103
+
104
+ Args:
105
+ base: A dictionary containing event fields such as `kind`, `stage`,
106
+ `data`, `tags`, `severity`, etc.
107
+ text: Optional preview text for the event. If None, it is derived
108
+ from the `data` field in `base`.
109
+ metrics: Optional dictionary of numeric metrics (e.g., latency,
110
+ token usage) to include in the event.
111
+
112
+ Returns:
113
+ Event: The fully constructed and persisted `Event` object.
114
+ """
115
+ ts = now_iso()
116
+
117
+ # Merge Scope dimensions
118
+ dims: dict[str, str] = {}
119
+ if self.scope is not None:
120
+ dims = self.scope.metering_dimensions()
121
+
122
+ run_id = base.get("run_id") or dims.get("run_id") or self.run_id
123
+ session_id = base.get("session_id") or dims.get("session_id") or self.session_id
124
+ scope_id = base.get("scope_id") or self.memory_scope_id or session_id or run_id
125
+
126
+ base.setdefault("run_id", run_id)
127
+ base.setdefault("scope_id", scope_id)
128
+ base.setdefault("session_id", session_id)
129
+ # ... (populate other fields from dims if needed) ...
130
+
131
+ severity = int(base.get("severity", 2))
132
+ signal = base.get("signal")
133
+ if signal is None:
134
+ signal = self._estimate_signal(text=text, metrics=metrics, severity=severity)
135
+
136
+ kind = base.get("kind") or "misc"
137
+
138
+ eid = stable_event_id(
139
+ {
140
+ "ts": ts,
141
+ "run_id": base["run_id"],
142
+ "kind": kind,
143
+ "text": (text or "")[:6000],
144
+ "tool": base.get("tool"),
145
+ }
146
+ )
147
+
148
+ evt = Event(
149
+ event_id=eid,
150
+ ts=ts,
151
+ run_id=run_id,
152
+ scope_id=scope_id,
153
+ kind=kind,
154
+ text=text,
155
+ data=base.get("data"),
156
+ tags=base.get("tags"),
157
+ metrics=metrics,
158
+ tool=base.get("tool"),
159
+ severity=severity,
160
+ signal=signal,
161
+ inputs=base.get("inputs"),
162
+ outputs=base.get("outputs"),
163
+ # ... pass other fields ...
164
+ version=2,
165
+ )
166
+
167
+ await self.hotlog.append(self.timeline_id, evt, ttl_s=self.hot_ttl_s, limit=self.hot_limit)
168
+ await self.persistence.append_event(self.timeline_id, evt)
169
+
170
+ # Metering hook
171
+ try:
172
+ meter = current_metering()
173
+ await meter.record_event(scope=self.scope, scope_id=scope_id, kind=f"memory.{kind}")
174
+ except Exception:
175
+ if self.logger:
176
+ self.logger.exception("Error recording metering event")
177
+
178
+ return evt
179
+
180
+ async def record(
181
+ self,
182
+ kind: str,
183
+ data: Any,
184
+ tags: list[str] | None = None,
185
+ severity: int = 2,
186
+ stage: str | None = None,
187
+ inputs_ref=None,
188
+ outputs_ref=None,
189
+ metrics: dict[str, float] | None = None,
190
+ signal: float | None = None,
191
+ text: str | None = None, # optional override
192
+ ) -> Event:
193
+ """
194
+ Record an event with common fields.
195
+
196
+ This method standardizes event creation by populating fields such as
197
+ `kind`, `severity`, `tags`, and `metrics`. It also supports optional
198
+ references for inputs and outputs, and allows for signal strength
199
+ overrides.
200
+
201
+ Examples:
202
+ Basic usage for a user action:
203
+ ```python
204
+ await context.memory().record(
205
+ kind="user_action",
206
+ data={"action": "clicked_button"},
207
+ tags=["ui", "interaction"]
208
+ )
209
+ ```
210
+
211
+ Recording a tool execution with metrics:
212
+ ```python
213
+ await context.memory().record(
214
+ kind="tool_call",
215
+ data={"tool": "search", "query": "weather"},
216
+ metrics={"latency": 0.123, "tokens_used": 45},
217
+ severity=3
218
+ )
219
+ ```
220
+
221
+ Args:
222
+ kind: Logical kind of event (e.g., `"user_msg"`, `"tool_call"`, `"chat_turn"`).
223
+ data: JSON-serializable content or string providing event details.
224
+ tags: A list of string labels for categorization. Defaults to None.
225
+ severity: An integer (1-3) indicating importance. Defaults to 2.
226
+ stage: Optional stage of the event (e.g., `"user"`, `"assistant"`, `"system"`). Defaults to None.
227
+ inputs_ref: Optional references for input values. Defaults to None.
228
+ outputs_ref: Optional references for output values. Defaults to None.
229
+ metrics: A dictionary of numeric metrics (e.g., latency, token usage). Defaults to None.
230
+ signal: Manual override for the signal strength (0.0 to 1.0). If None, it is calculated heuristically.
231
+ text: Optional preview text override. If None, it is derived from `data`.
232
+
233
+ Returns:
234
+ Event: The fully constructed and persisted `Event` object.
235
+
236
+ """
237
+
238
+ # 1) derive short preview text
239
+ if text is None and data is not None:
240
+ if isinstance(data, str):
241
+ text = data
242
+ else:
243
+ try:
244
+ raw = json.dumps(data, ensure_ascii=False)
245
+ text = raw
246
+ except Exception as e:
247
+ text = f"<unserializable data: {e!s}>"
248
+ if self.logger:
249
+ self.logger.warning(text)
250
+
251
+ # 2) optionally truncate preview text (enforce token discipline)
252
+ if text and len(text) > 2000:
253
+ text = text[:2000] + " …[truncated]"
254
+
255
+ # 3) full structured payload in Event.data when possible
256
+ data_field: dict[str, Any] | None = None
257
+ if isinstance(data, dict):
258
+ data_field = data
259
+ elif data is not None and not isinstance(data, str):
260
+ # store under "value" if it's JSON-serializable
261
+ try:
262
+ json.dumps(data, ensure_ascii=False)
263
+ data_field = {"value": data}
264
+ except Exception:
265
+ data_field = {"repr": repr(data)}
266
+
267
+ base: dict[str, Any] = dict(
268
+ kind=kind,
269
+ stage=stage,
270
+ severity=severity,
271
+ tags=tags or [],
272
+ data=data_field,
273
+ inputs=inputs_ref,
274
+ outputs=outputs_ref,
275
+ )
276
+ if signal is not None:
277
+ base["signal"] = signal
278
+
279
+ return await self.record_raw(base=base, text=text, metrics=metrics)
280
+
281
+ def _estimate_signal(
282
+ self, *, text: str | None, metrics: dict[str, Any] | None, severity: int
283
+ ) -> float:
284
+ score = 0.15 + 0.1 * severity
285
+ if text:
286
+ score += min(len(text) / 400.0, 0.4)
287
+ if metrics:
288
+ score += 0.2
289
+ return max(0.0, min(1.0, score))
290
+
291
+ async def build_prompt_segments(
292
+ self,
293
+ *,
294
+ recent_chat_limit: int = 12,
295
+ include_long_term: bool = True,
296
+ summary_tag: str = "session",
297
+ max_summaries: int = 3,
298
+ include_recent_tools: bool = False,
299
+ tool: str | None = None,
300
+ tool_limit: int = 10,
301
+ ) -> dict[str, Any]:
302
+ """
303
+ Assemble memory context for prompts, including long-term summaries,
304
+ recent chat history, and recent tool usage.
305
+
306
+ Examples:
307
+ Build prompt segments with default settings:
308
+ ```python
309
+ segments = await context.memory().build_prompt_segments()
310
+ ```
311
+
312
+ Include recent tool usage and filter by a specific tool:
313
+ ```python
314
+ segments = await context.memory().build_prompt_segments(
315
+ include_recent_tools=True,
316
+ tool="search",
317
+ tool_limit=5
318
+ )
319
+ ```
320
+
321
+ Args:
322
+ recent_chat_limit: The maximum number of recent chat messages to include.
323
+ Defaults to 12.
324
+ include_long_term: Whether to include long-term memory summaries.
325
+ Defaults to True.
326
+ summary_tag: The tag used to filter long-term summaries.
327
+ Defaults to "session".
328
+ max_summaries: The maximum number of long-term summaries to include.
329
+ Defaults to 3.
330
+ include_recent_tools: Whether to include recent tool usage.
331
+ Defaults to False.
332
+ tool: The specific tool to filter recent tool usage.
333
+ Defaults to None.
334
+ tool_limit: The maximum number of recent tool events to include.
335
+ Defaults to 10.
336
+
337
+ Returns:
338
+ dict[str, Any]: A dictionary containing the following keys:
339
+
340
+ - "long_term" (str): Combined long-term summary text or an empty
341
+ string if not included.
342
+
343
+ - "recent_chat" (list[dict[str, Any]]): A list of recent chat
344
+ messages, each represented as a dictionary with the following keys:
345
+ - "ts" (str): Timestamp of the message.
346
+ - "role" (str): Role of the sender (e.g., "user", "assistant").
347
+ - "text" (str): The content of the message.
348
+ - "tags" (list[str]): Tags associated with the message.
349
+
350
+ - "recent_tools" (list[dict[str, Any]]): A list of recent tool
351
+ usage events, each represented as a dictionary with the following keys:
352
+ - "ts" (str): Timestamp of the tool event.
353
+ - "tool" (str): Name of the tool used.
354
+ - "message" (str): Message or description of the tool event.
355
+ - "inputs" (Any): Inputs provided to the tool.
356
+ - "outputs" (Any): Outputs generated by the tool.
357
+ - "tags" (list[str]): Tags associated with the tool event.
358
+ """
359
+ long_term_text = ""
360
+ if include_long_term:
361
+ try:
362
+ summaries = await self.load_recent_summaries(
363
+ summary_tag=summary_tag,
364
+ limit=max_summaries,
365
+ )
366
+ except Exception:
367
+ summaries = []
368
+
369
+ parts: list[str] = []
370
+ for s in summaries:
371
+ st = s.get("summary") or s.get("text") or s.get("body") or s.get("value") or ""
372
+ if st:
373
+ parts.append(st)
374
+
375
+ if parts:
376
+ # multiple long-term summaries → concatenate oldest→newest
377
+ long_term_text = "\n\n".join(parts)
378
+
379
+ recent_chat = await self.recent_chat(limit=recent_chat_limit)
380
+
381
+ recent_tools: list[dict[str, Any]] = []
382
+ if include_recent_tools:
383
+ events = await self.recent_tool_results(
384
+ tool=tool,
385
+ limit=tool_limit,
386
+ )
387
+ for e in events:
388
+ recent_tools.append(
389
+ {
390
+ "ts": getattr(e, "ts", None),
391
+ "tool": e.tool,
392
+ "message": e.text,
393
+ "inputs": getattr(e, "inputs", None),
394
+ "outputs": getattr(e, "outputs", None),
395
+ "tags": list(e.tags or []),
396
+ }
397
+ )
398
+
399
+ return {
400
+ "long_term": long_term_text,
401
+ "recent_chat": recent_chat,
402
+ "recent_tools": recent_tools,
403
+ }
404
+
405
+ # ----- Stubs for future memory facade features -----
406
+ async def mark_event_important(
407
+ self,
408
+ event_id: str,
409
+ *,
410
+ reason: str | None = None,
411
+ topic: str | None = None,
412
+ ) -> None:
413
+ """
414
+ Stub / placeholder:
415
+
416
+ Mark a given event as "important" / "core_fact" for future policies.
417
+
418
+ Intended future behavior (not implemented yet):
419
+ - Look up the Event by event_id (via Persistence).
420
+ - Re-emit an updated Event with an added tag (e.g. "core_fact" or "pinned").
421
+ - Optionally promote to a fact artifact or RAG doc.
422
+
423
+ For now, this is a no-op / NotImplementedError to avoid surprise behavior.
424
+ """
425
+ raise NotImplementedError("mark_event_important is reserved for future memory policy")
426
+
427
+ async def save_core_fact_artifact(
428
+ self,
429
+ *,
430
+ scope_id: str,
431
+ topic: str,
432
+ fact_id: str,
433
+ content: dict[str, Any],
434
+ ):
435
+ """
436
+ Stub / placeholder:
437
+
438
+ Save a canonical, long-lived fact as a pinned artifact.
439
+ Intended future behavior:
440
+ - Use artifacts.save_json(...) to write the fact payload under a
441
+ stable path like file://mem/<scope_id>/facts/<topic>/<fact_id>.json
442
+ - Mark the artifact pinned in the index.
443
+ - Optionally write a tool_result Event referencing this artifact.
444
+
445
+ Not implemented yet; provided as an explicit extension hook.
446
+ """
447
+ raise NotImplementedError("save_core_fact_artifact is reserved for future memory policy")